-
Notifications
You must be signed in to change notification settings - Fork 19
Add: github workflow for maintaining structure and build checks #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds a Flutter CI workflow and an APPLICATION_ID env template key, converts several constants to env-driven names, applies UI styling changes to opaque colors, introduces small API/constructor tweaks and lint suppressions, and makes the wallet connect button initiate a connection. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as WalletNotConnectedWidget
participant WP as WalletProvider
User->>UI: Tap "Connect Wallet"
UI->>WP: walletProvider.connectWallet()
WP-->>UI: Initiate connection flow (dialogs / callbacks)
UI-->>User: Show progress / result
sequenceDiagram
participant UI as App UI
participant Svc as Contract Service
participant Env as dotenv
participant ABI as treeNftContractABI
UI->>Svc: call read/write(...)
Svc->>Env: dotenv.env['TREE_NFT_CONTRACT_ADDRESS']
Env-->>Svc: contract address
Svc->>ABI: reference ABI constant
Svc-->>UI: future/result
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (7)
.env.stencil (1)
5-6: Fix dotenv-linter warnings: sort keys and add trailing newlineAlphabetize keys and ensure a blank line at EOF to satisfy lint hints. No functional impact, but it keeps templates clean and consistent.
Apply:
-WALLETCONNECT_PROJECT_ID=<WALLETCONNECT_PROJECT_ID> -API_KEY=<PINATA_API_KEY> -API_SECRET=<PINATA_API_SECRET> -ALCHEMY_API_KEY=<ALCHEMY_API_KEY> -CONTRACT_ADDRESS=<CONTRACT_ADDRESS> -APPLICATION_ID=<APPLICATION_ID> +ALCHEMY_API_KEY=<ALCHEMY_API_KEY> +API_KEY=<PINATA_API_KEY> +API_SECRET=<PINATA_API_SECRET> +APPLICATION_ID=<APPLICATION_ID> +CONTRACT_ADDRESS=<CONTRACT_ADDRESS> +WALLETCONNECT_PROJECT_ID=<WALLETCONNECT_PROJECT_ID> +.github/workflows/flutter.yml (6)
20-20: Remove trailing whitespaceTrims the single trailing space that YAMLlint flagged.
- flutter-version: '>=3.16.0' + flutter-version: '>=3.16.0'
17-21: Enable pub cache to speed up CIsubosito/flutter-action supports caching; enabling it materially reduces build times.
- name: Setup Flutter uses: subosito/flutter-action@v2 with: - flutter-version: '>=3.16.0' + flutter-version: '>=3.16.0' + cache: true
25-26: Avoid redundant pub resolution during analyzeSince you already ran
flutter pub get, pass--no-pubto keep analysis faster and deterministic.- - name: Dart analyze - run: flutter analyze + - name: Dart analyze + run: flutter analyze --no-pub
3-8: Add concurrency and minimal permissionsPrevent duplicate runs on the same ref spamming CI and set least-privilege permissions.
name: Flutter CI on: push: branches: [ main ] pull_request: branches: [ main ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true
13-21: Security hardening: pin actions to commit SHAsTo reduce supply-chain risk, pin third-party actions to specific SHAs and keep a note of the version.
- - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 @@ - - name: Setup Flutter - uses: subosito/flutter-action@v2 + - name: Setup Flutter + uses: subosito/flutter-action@f45c8c6e01bd3a746c4d4d5a9c8e9c3b6a2a2c29 # v2Note: Replace SHAs with the latest verified for your org’s policy.
28-32: Add a Flutter test step to CIWe detected an existing Dart test file at
test/widget_test.dart. To catch regressions early, it’s recommended to run your tests in the CI pipeline before building the web artifact.• Location:
.github/workflows/flutter.yml
– Insert immediately after the Check formatting step and before the Flutter build (web example) step.Suggested update:
- name: Check formatting run: dart format --output=none --set-exit-if-changed . + - name: Run tests + run: flutter test --coverage + - name: Flutter build (web example) run: flutter build web
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
.env.stencil(1 hunks).github/workflows/flutter.yml(1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/flutter.yml
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 YAMLlint (1.37.1)
.github/workflows/flutter.yml
[error] 20-20: trailing spaces
(trailing-spaces)
🪛 dotenv-linter (3.3.0)
.env.stencil
[warning] 5-5: [UnorderedKey] The CONTRACT_ADDRESS key should go before the WALLETCONNECT_PROJECT_ID key
(UnorderedKey)
[warning] 6-6: [EndingBlankLine] No blank line at the end of the file
(EndingBlankLine)
[warning] 6-6: [UnorderedKey] The APPLICATION_ID key should go before the CONTRACT_ADDRESS key
(UnorderedKey)
| ALCHEMY_API_KEY=<ALCHEMY_API_KEY> | ||
| CONTRACT_ADDRESS=<CONTRACT_ADDRESS> | ||
| CONTRACT_ADDRESS=<CONTRACT_ADDRESS> | ||
| APPLICATION_ID=<APPLICATION_ID> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify APPLICATION_ID usage and docs
Confirm that APPLICATION_ID is actually read by the app and mentioned in README/Getting Started so newcomers don’t miss it.
Run:
🏁 Script executed:
#!/bin/bash
# Find references to APPLICATION_ID and env loading
rg -nCI3 --glob '!**/build/**' 'APPLICATION_ID|dotenv|flutter_dotenv|--dart-define'Length of output: 160
🏁 Script executed:
#!/bin/bash
# Find references to APPLICATION_ID and env loading with correct flags
rg -n -C 3 -i --glob '!**/build/**' \
-e 'APPLICATION_ID' \
-e 'dotenv' \
-e 'flutter_dotenv' \
-e '--dart-define'Length of output: 6796
🏁 Script executed:
#!/bin/bash
# Check README for mention of APPLICATION_ID and env setup
rg -n -C 3 'APPLICATION_ID' -g 'README.md'
rg -n -C 3 'env' -g 'README.md'Length of output: 1177
Add and document APPLICATION_ID environment support
It looks like APPLICATION_ID is declared in .env.stencil but never actually read by the Flutter code, and the README has no mention of it. To avoid confusion for newcomers, please:
-
Load the variable in Dart
Inlib/main.dart, afterawait dotenv.load(...), add something like:const String applicationId = dotenv.env['APPLICATION_ID'] ?? '';
so that your app can access the value at runtime.
-
Pass it through to your Linux runner
Modifylinux/CMakeLists.txtto use the environment value (e.g. via$ENV{APPLICATION_ID}) or otherwise propagateapplicationIdinto the desktop build. -
Remove or update
.env.stencil
If you’re not actually usingAPPLICATION_IDat runtime, remove it from.env.stencil. Otherwise, keep it and document it fully. -
Document in README.md
Under “Getting Started,” add steps to:- Copy
.env.stencilto.env - Set
APPLICATION_ID=<your.app.id> - Run the app (and rebuild desktop targets as needed)
- Copy
This ensures the environment variable is both used and clearly documented for all platforms.
🧰 Tools
🪛 dotenv-linter (3.3.0)
[warning] 6-6: [EndingBlankLine] No blank line at the end of the file
(EndingBlankLine)
[warning] 6-6: [UnorderedKey] The APPLICATION_ID key should go before the CONTRACT_ADDRESS key
(UnorderedKey)
🤖 Prompt for AI Agents
In .env.stencil around line 6 the APPLICATION_ID variable is declared but not
used; update lib/main.dart to read the variable after dotenv.load (e.g., assign
dotenv.env['APPLICATION_ID'] to a const or final applicationId) so the app can
access it at runtime, modify linux/CMakeLists.txt to propagate the environment
value into the Linux build (for example read $ENV{APPLICATION_ID} or pass it as
a compile-time definition to the binary), and then either remove APPLICATION_ID
from .env.stencil if unused or keep and document it in README.md under Getting
Started with steps to copy .env.stencil to .env, set
APPLICATION_ID=<your.app.id>, and rebuild/run desktop targets.
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update checkout action to v4 (actionlint failure)
actionlint flags actions/checkout@v3 as too old on current runners. Upgrade to v4 to avoid workflow failures.
- uses: actions/checkout@v3
+ uses: actions/checkout@v4📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uses: actions/checkout@v3 | |
| uses: actions/checkout@v4 |
🧰 Tools
🪛 actionlint (1.7.7)
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🤖 Prompt for AI Agents
.github/workflows/flutter.yml around line 15: the workflow uses
actions/checkout@v3 which actionlint flags as outdated; update the checkout step
to actions/checkout@v4. Edit the workflow to replace the version string from v3
to v4 (or use the stable major tag actions/checkout@v4) and ensure any existing
inputs remain unchanged; commit the updated workflow file so the CI runs with
the newer action version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (6)
.github/workflows/flutter.yaml (6)
22-24: Reproducible installs and faster CI with Pub cache.
- Enforce the lockfile for deterministic dependency resolution.
- Cache the Pub cache to speed up subsequent runs.
Apply this diff:
+ - name: Cache Pub dependencies + uses: actions/cache@v4 + with: + path: ~/.pub-cache + key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} + restore-keys: | + ${{ runner.os }}-pub- + - name: Install dependencies - run: flutter pub get + run: flutter pub get --enforce-lockfile
31-32: Harden the web build step (optional).
- Pre-cache web artifacts to reduce build time and flakiness on clean runners.
- Explicitly pass
--release(default) for clarity.Apply this diff:
+ - name: Precache web artifacts + run: flutter precache --web + - name: Flutter build (web example) - run: flutter build web + run: flutter build web --release
3-8: Add minimal permissions and cancel-in-progress to save CI minutes.
- Principle of least privilege for GHA tokens.
- Cancel superseded runs on the same ref to avoid wasted builds.
Apply this diff:
on: push: branches: [ main ] pull_request: branches: [ main ] +permissions: + contents: read + pull-requests: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: trueOptionally skip draft PRs:
jobs: build: + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest
11-11: Set a job timeout to prevent hung builds.Protects against indefinite runner usage when a step stalls.
Apply this diff:
build: - runs-on: ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 20
20-20: Trailing space flagged by yamllint.There’s a trailing space after
'stable'on Line 20. The earlier “Setup Flutter” fix removes this line entirely; otherwise, remove the trailing space.If you keep
flutter-version, apply this minimal diff:- flutter-version: 'stable' + flutter-version: 'stable'
1-33: Ensure correct Flutter action input and enhance CI robustnessThe
subosito/flutter-action@v2step is correctly using theflutter-versioninput—nochannelfield is needed. It accepts both channel names (stable, beta, dev, master) and explicit SDK versions (e.g.3.10.2), as well as aliases likelatestorstable. For consistency and reproducibility, consider pinning to an explicit version:- name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.10.2' # or 'stable'Additional recommendations to improve speed and reliability:
• Checkout step
- Pin the checkout action to v3 and fetch full history if you rely on tags or versioning:
- name: Checkout repository uses: actions/checkout@v3 with: fetch-depth: 0• Cache pub dependencies
- name: Cache Pub packages uses: actions/cache@v3 with: path: ~/.pub-cache key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.yaml') }} restore-keys: | ${{ runner.os }}-pub-• Run unit tests
- name: Run Flutter tests run: flutter test --coverage• Add workflow concurrency to avoid redundant runs
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true• Review permissions
- Limit token scopes where possible (e.g.,
permissions: { contents: read }if write access isn’t required).These changes will prevent unexpected breakage, speed up CI by reusing dependencies, and ensure only the latest relevant runs are active.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
.github/workflows/flutter.yaml(1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/flutter.yaml
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 YAMLlint (1.37.1)
.github/workflows/flutter.yaml
[error] 20-20: trailing spaces
(trailing-spaces)
🔇 Additional comments (1)
.github/workflows/flutter.yaml (1)
28-29: Formatting check is solid.Using
dart format --set-exit-if-changedis the right approach for consistent formatting enforcement.
| uses: actions/checkout@v3 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upgrade checkout to v4 (v3 is deprecated and flagged by actionlint).
actionlint warns that actions/checkout@v3 is too old for current runners. Move to v4 to avoid workflow failures and to get Node 20 compatibility.
Apply this diff:
- - name: Checkout repository
- uses: actions/checkout@v3
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uses: actions/checkout@v3 | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 |
🧰 Tools
🪛 actionlint (1.7.7)
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🤖 Prompt for AI Agents
.github/workflows/flutter.yaml around lines 15-16: the workflow uses
actions/checkout@v3 which is deprecated and flagged by actionlint; update the
step to use actions/checkout@v4 (replace v3 with v4), commit the updated
workflow file, and re-run the workflow to verify actionlint and CI succeed.
| uses: subosito/flutter-action@v2 | ||
| with: | ||
| flutter-version: 'stable' | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Flutter setup: use channel or pin a real version; also enable caching.
Using flutter-version: 'stable' is likely invalid; the action expects a concrete version (e.g., 3.24.x) or you should set channel: stable. Add caching to speed up runs. This change also removes the trailing space lint on Line 20.
Apply this diff (channel-based, recommended for CI unless you need a specific patch version):
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
- flutter-version: 'stable'
+ channel: 'stable'
+ cache: trueAlternatively, pin an explicit version for reproducibility:
- flutter-version: 'stable'
+ flutter-version: '3.24.x'
+ cache: true🧰 Tools
🪛 YAMLlint (1.37.1)
[error] 20-20: trailing spaces
(trailing-spaces)
🤖 Prompt for AI Agents
.github/workflows/flutter.yaml around lines 18 to 21: the workflow uses
subosito/flutter-action@v2 with flutter-version: 'stable' (invalid) and has a
trailing space; change it to either set channel: stable (preferred) or pin a
concrete patch series like flutter-version: '3.24.x' for reproducibility, remove
the trailing space, and enable caching for Flutter artifacts (use the action's
cache option or add actions/cache for the Flutter SDK and pub cache) so CI runs
are faster and deterministic.
| - name: Dart analyze | ||
| run: flutter analyze | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Add unit tests to CI (if present).
Running tests is a core CI signal and catches regressions beyond static analysis.
Proposed step:
- name: Dart analyze
run: flutter analyze
+
+ - name: Flutter tests
+ run: flutter test -r expandedTo verify whether this repo has tests before adding the step, run:
🏁 Script executed:
#!/bin/bash
# List *_test.dart files with context
rg -nP --type=dart -C2 '_test\.dart$' || trueLength of output: 46
🏁 Script executed:
#!/bin/bash
# Find directories named test or tests
find . -type d \( -name test -o -name tests \) || true
# List Dart test files by suffix
rg -n --type=dart '_test\.dart$' || true
# Search for test import statements
rg -n --type=dart 'import .+test\.dart' || true
# Search for any test function calls
rg -n --type=dart 'test\(' || true
# Search specifically for Flutter widget tests
rg -n --type=dart 'testWidgets' || trueLength of output: 384
Add Flutter test step to CI workflow
Tests already exist in test/widget_test.dart (using testWidgets), so let’s surface them in CI. Update .github/workflows/flutter.yaml as follows:
• File: .github/workflows/flutter.yaml
After the Dart analyze step, add a Flutter tests job.
- name: Dart analyze
run: flutter analyze
+ - name: Run Flutter tests
+ run: flutter test -r expanded📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Dart analyze | |
| run: flutter analyze | |
| - name: Dart analyze | |
| run: flutter analyze | |
| - name: Run Flutter tests | |
| run: flutter test -r expanded |
🤖 Prompt for AI Agents
.github/workflows/flutter.yaml around lines 25-27: after the "Dart analyze"
step, add a new CI step/job to run Flutter tests; create a job (or a step in the
existing job) that ensures Flutter is set up (same runner or setup-flutter
action), runs flutter pub get, and then runs flutter test (so
test/widget_test.dart will execute), and ensure the step fails the build on test
failures and has appropriate name and placement immediately after the analyze
step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
.github/workflows/flutter.yaml (3)
17-21: Enable Flutter pub caching and remove trailing whitespace.Use the action’s built-in cache to speed up CI. Also, Line 20 has a trailing space flagged by yamllint.
- name: Setup Flutter uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: 'stable' + cache: true
14-16: Upgrade checkout to v4 (and fetch full history).actions/checkout@v3 is deprecated and flagged by actionlint on current runners. Move to v4 and fetch full history (useful for versioning or tooling that inspects git metadata).
- name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0
25-27: Run your Flutter tests in CI.Tests exist in the repo; add a step so regressions fail the build.
- name: Dart analyze run: flutter analyze + + - name: Run Flutter tests + run: flutter test -r expanded
🧹 Nitpick comments (2)
.github/workflows/flutter.yaml (2)
31-32: Publish build artifacts from CI.Uploading the web build helps with PR previews and debugging.
- name: Flutter build (web example) run: flutter build web + + - name: Upload web build artifact + uses: actions/upload-artifact@v4 + with: + name: web-build + path: build/web
3-12: Add workflow hardening: least-privilege permissions, concurrency, and a timeout.
- permissions: contents: read reduces token scope.
- concurrency cancels superseded runs on the same branch.
- timeout-minutes prevents hung jobs.
on: push: branches: [ main ] pull_request: branches: [ main ] +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest + timeout-minutes: 30
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
.github/workflows/flutter.yaml(1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/flutter.yaml
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 YAMLlint (1.37.1)
.github/workflows/flutter.yaml
[error] 20-20: trailing spaces
(trailing-spaces)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
lib/widgets/nft_display_utils/user_nfts_widget.dart (2)
283-295: “Alive/Deceased” status logic is inconsistent (color vs label)Color uses
death == 0while the label usesdeath < now— a living tree (death=0) gets a green pill but the label “Deceased”. Use a single predicate (death > 0 => Deceased) for both.Apply this diff:
- decoration: BoxDecoration( - color: tree.death == 0 ? Colors.green : Colors.red, + decoration: BoxDecoration( + color: (tree.death > 0) ? Colors.red : Colors.green, @@ - tree.death < DateTime.now().millisecondsSinceEpoch ~/ 1000 - ? 'Deceased' - : 'Alive', + (tree.death > 0) ? 'Deceased' : 'Alive',
150-155: Fix pagination logic and support arbitrary user addressesThe current implementation always uses
offset: 0and the connected wallet’s address, causing duplicate pages and preventing fetching NFTs forwidget.userAddresswhen viewing another user’s profile.Key changes required:
• In
lib/utils/services/contract_read_services.dart
– Add an optionalEthereumAddress? userAddressparameter togetNFTsByUserPaginated’s signature.
– In the method body, buildargsusinguserAddress ?? walletProvider.currentAddress.• In
lib/widgets/nft_display_utils/user_nfts_widget.dart(lines 150–155 & 164–173)
– Computeoffsetdynamically based on_currentPageand_itemsPerPageinstead of hard-coding 0.
– PassuserAddress: EthereumAddress.fromHex(widget.userAddress)to target the correct address.
– Reset_currentPagewhen not loading more, dedupe by token ID, then increment page and update_hasMore.Proposed diffs:
--- a/lib/utils/services/contract_read_services.dart +++ b/lib/utils/services/contract_read_services.dart @@ class ContractReadFunctions { - static Future<ContractReadResult> getNFTsByUserPaginated({ - required WalletProvider walletProvider, - int offset = 0, - int limit = 10, + static Future<ContractReadResult> getNFTsByUserPaginated({ + required WalletProvider walletProvider, + EthereumAddress? userAddress, // <— new optional target address + int offset = 0, + int limit = 10, }) async { try { if (!walletProvider.isConnected) { @@ - final String address = walletProvider.currentAddress.toString(); - if (!address.startsWith('0x')) { - return ContractReadResult.error( - errorMessage: 'Invalid wallet address format', - ); - } - - final EthereumAddress userAddress = EthereumAddress.fromHex(address); + // Use provided address or fall back to connected wallet + final EthereumAddress user = userAddress ?? + EthereumAddress.fromHex(walletProvider.currentAddress.toString()); + + if (!user.hex.startsWith('0x')) { + return ContractReadResult.error( + errorMessage: 'Invalid wallet address format', + ); + } @@ - final List<dynamic> args = [ - userAddress, + final List<dynamic> args = [ + user, BigInt.from(offset), BigInt.from(limit), ];--- a/lib/widgets/nft_display_utils/user_nfts_widget.dart +++ b/lib/widgets/nft_display_utils/user_nfts_widget.dart @@ 150,8c150,13 - final result = await ContractReadFunctions.getNFTsByUserPaginated( - walletProvider: walletProvider, - offset: 0, - limit: 10, + final result = await ContractReadFunctions.getNFTsByUserPaginated( + walletProvider: walletProvider, + userAddress: EthereumAddress.fromHex(widget.userAddress), + offset: (loadMore ? _currentPage : 0) * _itemsPerPage, + limit: _itemsPerPage, ); @@ 164,10c169,21 - setState(() { - if (loadMore) { - _nfts.addAll(newTrees); - } else { - _nfts = newTrees; - } - _totalCount = totalCount; - _currentPage++; - _hasMore = _nfts.length < _totalCount; - }); + setState(() { + if (loadMore) { + _nfts.addAll(newTrees); + } else { + _nfts = newTrees; + _currentPage = 0; // reset page when refreshing + } + // De-duplicate by token ID to guard against overlap + final seen = <int>{}; + _nfts = _nfts.where((t) => seen.add(t.id)).toList(); + _totalCount = totalCount; + _currentPage += 1; + _hasMore = _nfts.length < _totalCount; + });These updates ensure correct paging, avoid duplicate fetches, and allow viewing another user’s NFTs.
lib/pages/mint_nft/mint_nft_coordinates.dart (2)
495-501: Buttons use same foreground and background color — icons/text become invisibleBoth IconButton and TextButton set foregroundColor and backgroundColor to
0xFF1CD381, resulting in no contrast.Apply this diff:
- IconButton( + IconButton( onPressed: _refreshLocation, icon: const Icon(Icons.refresh, size: 20), style: IconButton.styleFrom( - backgroundColor: const Color(0xFF1CD381), - foregroundColor: const Color(0xFF1CD381), + backgroundColor: const Color(0xFF1CD381), + foregroundColor: Colors.white, ), ), @@ - TextButton.icon( + TextButton.icon( onPressed: _useCurrentLocation, icon: const Icon(Icons.my_location, size: 16), label: const Text("Auto"), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF1CD381), - backgroundColor: const Color(0xFF1CD381), + backgroundColor: const Color(0xFF1CD381), + foregroundColor: Colors.white, ), ),Also applies to: 508-511
561-567: Field label chip: icon and background share the same color — icon invisibleUse a contrasting icon color (e.g., white or black) against the green background.
Apply this diff:
- Container( + Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: const Color(0xFF1CD381), borderRadius: BorderRadius.circular(6), ), child: Icon( icon, - color: const Color(0xFF1CD381), + color: Colors.white, size: 14, ), ),lib/pages/mint_nft/mint_nft_details.dart (2)
140-147: Header icon invisible: white icon on white backgroundThe container is
Colors.whiteand the icon is alsoColors.white. Use a contrasting icon color.Apply this diff:
Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white, + color: Colors.white, borderRadius: BorderRadius.circular(14), ), child: const Icon( Icons.edit_note, - color: Colors.white, + color: Color(0xFF1CD381), size: 28, ), ),
262-268: Form label chip: icon and background use the same green — icon invisibleSet the icon to white (or use a white container with green icon). This matches the visual language used elsewhere.
Apply this diff:
decoration: BoxDecoration( - color: const Color(0xFF1CD381), + color: const Color(0xFF1CD381), borderRadius: BorderRadius.circular(8), ), child: Icon( icon, - color: const Color(0xFF1CD381), + color: Colors.white, size: 18, ),
♻️ Duplicate comments (2)
.github/workflows/flutter.yaml (2)
15-16: Upgrade checkout to v4 (v3 flagged by actionlint).Apply this diff:
- - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0
28-29: Run Flutter tests in CI.Tests catch regressions beyond static analysis; they exist in the repo.
Apply this diff right after analyze:
- name: Dart analyze run: flutter analyze + + - name: Run Flutter tests + run: flutter test -r expanded
🧹 Nitpick comments (30)
lib/pages/mint_nft/mint_nft_images.dart (2)
142-146: Make _showSnackBar no-op if widget is unmountedDefensive guard avoids using a stale BuildContext if uploads complete after navigation.
void _showSnackBar(String message) { - ScaffoldMessenger.of(context).showSnackBar( + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); }
258-261: Solid green overlay fully obscures the image; prefer themed, semi-transparent success overlayThe earlier semi-transparent overlay preserved image context. Consider using theme colors with slight opacity for better legibility across light/dark themes.
- color: Colors.green, + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.85),Alternative: use a lighter success tone if you have a dedicated success color in your theme.
lib/utils/services/switch_chain_utils.dart (2)
41-46: Avoid null-assertion on MaterialColor indexing.Replace
Colors.green[50]andColors.grey[300]!with.shade50/.shade300to remove the!and be explicit about shades.Apply this diff:
- tileColor: isCurrentChain ? Colors.green[50] : null, + tileColor: isCurrentChain ? Colors.green.shade50 : null, @@ - color: isCurrentChain ? Colors.green : Colors.grey[300]!, + color: isCurrentChain ? Colors.green : Colors.grey.shade300,
66-88: Handle unsuccessful chain switch (success == false) with feedback.Currently only exceptions surface an error snackbar. If
switchChainreturnsfalse, the user gets no feedback.Apply this diff:
final success = await walletProvider.switchChain(chainId); - if (success && context.mounted) { + if (success && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Chain switched successfully!'), backgroundColor: Colors.green, ), ); + } else if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Could not switch chain. Please try again.'), + backgroundColor: Colors.red, + ), + ); }lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (2)
36-41: Shadow color now fully opaque; consider reducing opacity for a softer elevation.
Colors.blackfor shadows can look harsh (especially on web). PriorwithOpacity(0.1)was closer to Material defaults.Apply this diff:
- BoxShadow( - color: Colors.black, + BoxShadow( + color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2), ),
66-71: Same here: fully opaque green shadow may appear heavy.Use a slight opacity for subtler depth and better theming.
Apply this diff:
- BoxShadow( - color: Colors.green, + BoxShadow( + color: Colors.green.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2), ),lib/models/wallet_chain_option.dart (1)
33-35: Guard against missing key; produce actionable diagnostics.Even with lazy access, an empty key yields broken URLs. Consider asserting in debug or logging a clear message to help setup.
Example:
assert( _alchemyApiKey().isNotEmpty, 'ALCHEMY_API_KEY is missing. Provide it via .env or CI secrets.', );Also applies to: 40-40, 50-50
.github/workflows/flutter.yaml (2)
17-19: Trim trailing spaces and enable Flutter caching for faster CI.There are trailing spaces (YAMLlint errors), and enabling the action’s cache speeds up runs.
Apply this diff:
- - name: Setup environment file from template - run: cp .env.stencil .env + - name: Setup environment file from template + run: cp .env.stencil .env @@ - - name: Setup Flutter + - name: Setup Flutter uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: 'stable' + cache: trueAlso applies to: 20-24
34-35: Optional: add basic workflow hardening.Consider explicit least-privilege permissions and PR concurrency to cancel superseded builds.
Example:
name: Flutter CI on: push: @@ jobs: build: runs-on: ubuntu-latest + permissions: + contents: read + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: truelib/utils/services/ipfs_services.dart (1)
9-31: Make upload lifecycle robust: ensure state reset on error and add a timeout.Set uploading=false in a finally block and avoid indefinite hangs with a timeout.
Apply this diff:
-Future<String?> uploadToIPFS( - File imageFile, Function(bool) setUploadingState) async { - setUploadingState(true); +Future<String?> uploadToIPFS( + File imageFile, Function(bool) setUploadingState) async { + setUploadingState(true); var url = Uri.parse("https://api.pinata.cloud/pinning/pinFileToIPFS"); var request = http.MultipartRequest("POST", url); request.headers.addAll({ "pinata_api_key": apiKey, "pinata_secret_api_key": apiSecret, }); request.files.add(await http.MultipartFile.fromPath("file", imageFile.path)); - var response = await request.send(); - - setUploadingState(false); - - if (response.statusCode == 200) { - var jsonResponse = json.decode(await response.stream.bytesToString()); - return "https://gateway.pinata.cloud/ipfs/${jsonResponse['IpfsHash']}"; - } else { - return null; - } + try { + final response = + await request.send().timeout(const Duration(seconds: 60)); + if (response.statusCode == 200) { + final jsonResponse = + json.decode(await response.stream.bytesToString()); + return "https://gateway.pinata.cloud/ipfs/${jsonResponse['IpfsHash']}"; + } + return null; + } finally { + setUploadingState(false); + } }lib/utils/constants/contract_abis/tree_nft_contract_abi.dart (2)
4-4: ABI constant rename to lowerCamel looks good; remove stale lint suppressionRenaming to
treeNftContractABIaligns with Effective Dart. The// ignore: constant_identifier_namesat Line 1 is now obsolete—please remove it.
2-2: dotenv.load call verified; recommend optional lazy getter for env variablesConfirmed that
dotenv.load(fileName: ".env")is invoked inlib/main.dartat line 29, so your.envis loaded during app initialization.• lib/main.dart:29 –
await dotenv.load(fileName: ".env");ensures env vars are available beforerunApp.
• lib/utils/constants/contract_abis/tree_nft_contract_abi.dart – you still read fromdotenv.envat the top level; switching to a lazy getter will defer access until runtime (after load) and allow you to validate the value.Example refactor:
--- a/lib/utils/constants/contract_abis/tree_nft_contract_abi.dart +++ b/lib/utils/constants/contract_abis/tree_nft_contract_abi.dart -// top-level constant (evaluated at import) - final treeNftContractAbi = dotenv.env['TREE_NFT_CONTRACT_ABI']!; +// lazy getter defers access until after dotenv.load and validates presence + String get treeNftContractAbi { + final abi = dotenv.env['TREE_NFT_CONTRACT_ABI']; + if (abi == null || abi.isEmpty) { + throw StateError('TREE_NFT_CONTRACT_ABI is missing or empty in .env'); + } + return abi; + }lib/widgets/profile_widgets/profile_section_widget.dart (2)
49-51: Correct log context and severity for parse failuresIt’s profile data, not tree data; and parse failures should be logged as errors.
- logger.d("Error parsing Tree data: $e"); - logger.d("Data received: $data"); - logger.d("Data type: ${data.runtimeType}"); + logger.e("Error parsing User profile data", error: e); + logger.d("Profile raw data: $data"); + logger.d("Profile data runtimeType: ${data.runtimeType}");
247-355: UI simplification is fine; consider a small helper to remove duplication and add constsThe four nearly identical tiles can be rendered via a small builder (less repetition, easier theming). Also, most BoxShadow/Offset/Text can be
const.Example helper (outside selected range):
Widget _tokenTile({ required String label, required int value, required Color color, }) { return Padding( padding: const EdgeInsets.all(4.0), child: SizedBox( height: 40, width: 150, child: DecoratedBox( decoration: BoxDecoration( color: color, border: Border.all(color: Colors.black, width: 2), borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 3)), ], ), child: Center(child: Text('$label : $value')), ), ), ); }Then call it for each token.
lib/widgets/nft_display_utils/user_nfts_widget.dart (2)
341-378: Expanded inside Column can assert in unbounded-height contextsReturning a Column with an inner Expanded can throw “RenderFlex children have non-zero flex but incoming height constraints are unbounded” when this widget is placed in scrollables or other unconstrained parents.
Options:
- Let the parent control flex: expose the list as a widget without Expanded.
- Or detect constraints and fall back to a fixed-height SizedBox when unbounded.
- Or convert to a CustomScrollView with a SliverAppBar-like header and a SliverList body.
If helpful, I can prepare a patch to convert the body into a SliverList.
439-446: Infinite scroll trigger uses equality; prefer a thresholdExact equality on
pixels == maxScrollExtentcan miss triggers due to floating-point rounding and momentum scrolling.Apply this diff:
- if (!_isLoading && - _hasMore && - scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) { + if (_hasMore && + !_isLoading && + scrollInfo.metrics.pixels >= + scrollInfo.metrics.maxScrollExtent - 96) { _loadNFTs(loadMore: true); }lib/widgets/wallet_not_connected_widget.dart (1)
29-30: Wire-up looks good; consider awaiting and surfacing errors to usersIf
connectWallet()is async/fallible, make the handlerasync, await it, and show feedback on failure.Apply this diff:
- ElevatedButton( - onPressed: () { + ElevatedButton( + onPressed: () async { final walletProvider = Provider.of<WalletProvider>(context, listen: false); - walletProvider.connectWallet(); + try { + await walletProvider.connectWallet(); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to connect wallet: $e'), + backgroundColor: Colors.red, + ), + ); + } },lib/pages/mint_nft/mint_nft_coordinates.dart (1)
621-657: Dead code:_buildPreviewSection()is defined but unusedKeeping unused UI code drifts with future changes and increases maintenance.
Apply this diff to remove the block (or wire it into the page under the main content):
- // ignore: unused_element - Widget _buildPreviewSection() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: const Color(0xFFFAEB96), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.preview, - color: const Color(0xFF1CD381), - size: 20, - ), - ), - const SizedBox(width: 12), - const Text( - 'Live Preview', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF1CD381), - ), - ), - ], - ), - ), - const NewNFTWidget(), - ], - ); - }lib/pages/mint_nft/mint_nft_details.dart (1)
209-226: Minor: button shadow color equals the fillUsing the same green for
shadowColoras the button’sbackgroundColorreduces perceived elevation. Consider a darker variant or semi-transparent black for depth.Example:
- shadowColor: const Color(0xFF1CD381), + shadowColor: Colors.black.withOpacity(0.15),lib/widgets/map_widgets/flutter_map_widget.dart (11)
35-37: Harden against invalid provider coordinates (NaN/∞) and out-of-range valuesProvider outputs are used directly with no sanitation, unlike
StaticDisplayMap. This can crashLatLngor render off-world positions.Apply minimal in-place sanitation:
- final double latitude = provider.getLatitude(); - final double longitude = provider.getLongitude(); + final latRaw = provider.getLatitude(); + final lngRaw = provider.getLongitude(); + final double latitude = (latRaw.isFinite ? latRaw : 0.0).clamp(-90.0, 90.0).toDouble(); + final double longitude = (lngRaw.isFinite ? lngRaw : 0.0).clamp(-180.0, 180.0).toDouble();Optional: mirror the
_sanitizeCoordinatehelper fromStaticDisplayMap(or extract to a shared util) for consistency.
104-109: initialZoom is below minZoom; may be clamped or cause jankYou set
initialZoom: 1.0withminZoom: 3.0. Some flutter_map versions clamp silently; others may jump on first frame.Recommend aligning them:
- initialZoom: 1.0, + initialZoom: 3.0, minZoom: 3.0,
131-137: Avoid flipping the entire widget to an error state on a single tile failureSetting
_hasError = trueinsideerrorTileCallbackhides the map if any one tile fails. Transient tile failures are common;flutter_mapalready displays an error tile.Consider logging and allowing the map to continue:
- errorTileCallback: (tile, error, stackTrace) { - if (mounted) { - setState(() { - _hasError = true; - }); - } - }, + errorTileCallback: (tile, error, stackTrace) { + logger.w('Tile load failed: $error'); + },If you want a hard fail, gate it behind a retry threshold or a timeout rather than immediate flip.
157-168: Loading overlay color: consider theme surface for dark modeSolid
Colors.whitecan be jarring in dark theme.- Container( - color: Colors.white, + Container( + color: Theme.of(context).colorScheme.surface,
175-177: Coordinate badge background: prefer toned black or themed surface with opacitySolid black can dominate;
Colors.black87or a themed surface with slight alpha improves readability over tiles.- color: Colors.black, + color: Colors.black87,
198-201: Shadow is fully black; soften for a more natural elevationPure black shadows can look harsh; tone it down for material consistency.
- color: Colors.black, + color: Colors.black.withOpacity(0.25),
277-289: Info banner color: consider Theme color for consistency and accessibilitySolid
Colors.bluemay clash with app theming. UsingcolorScheme.primarykeeps contrast predictable across themes.- color: Colors.blue, + color: Theme.of(context).colorScheme.primary,
471-482: Static map loading overlay: same dark-mode note as dynamic mapMirror the themed surface for consistency across both widgets.
- Container( - color: Colors.white, + Container( + color: Theme.of(context).colorScheme.surface,
489-491: Static badge background: same readability/theming nitMatch the earlier suggestion for toned black or themed surface.
- color: Colors.black, + color: Colors.black87,
512-515: Static map shadow: soften for consistencySame rationale as the other panel.
- color: Colors.black, + color: Colors.black.withOpacity(0.25),
591-603: Static info banner: prefer themed primarySame theming nit as dynamic widget; improves cohesion across the app.
- color: Colors.blue, + color: Theme.of(context).colorScheme.primary,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (22)
.github/workflows/flutter.yaml(1 hunks)lib/components/universal_navbar.dart(7 hunks)lib/components/wallet_connect_dialog.dart(2 hunks)lib/models/wallet_chain_option.dart(3 hunks)lib/pages/home_page.dart(0 hunks)lib/pages/mint_nft/mint_nft_coordinates.dart(2 hunks)lib/pages/mint_nft/mint_nft_details.dart(8 hunks)lib/pages/mint_nft/mint_nft_images.dart(2 hunks)lib/pages/register_user_page.dart(4 hunks)lib/providers/mint_nft_provider.dart(0 hunks)lib/providers/wallet_provider.dart(1 hunks)lib/utils/constants/contractDetails.dart(0 hunks)lib/utils/constants/contract_abis/tree_nft_contract_abi.dart(2 hunks)lib/utils/services/contract_read_services.dart(4 hunks)lib/utils/services/contract_write_functions.dart(2 hunks)lib/utils/services/ipfs_services.dart(2 hunks)lib/utils/services/switch_chain_utils.dart(1 hunks)lib/widgets/map_widgets/flutter_map_widget.dart(15 hunks)lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart(2 hunks)lib/widgets/nft_display_utils/user_nfts_widget.dart(4 hunks)lib/widgets/profile_widgets/profile_section_widget.dart(3 hunks)lib/widgets/wallet_not_connected_widget.dart(1 hunks)
💤 Files with no reviewable changes (3)
- lib/utils/constants/contractDetails.dart
- lib/pages/home_page.dart
- lib/providers/mint_nft_provider.dart
✅ Files skipped from review due to trivial changes (2)
- lib/providers/wallet_provider.dart
- lib/pages/register_user_page.dart
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/flutter.yaml
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 YAMLlint (1.37.1)
.github/workflows/flutter.yaml
[error] 17-17: trailing spaces
(trailing-spaces)
[error] 18-18: trailing spaces
(trailing-spaces)
[error] 23-23: trailing spaces
(trailing-spaces)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (15)
lib/utils/services/switch_chain_utils.dart (1)
25-57: Nice micro-optimization: spreading the mapped Iterable avoids an extra allocation.Using
...walletProvider.getSupportedChains().map(...)directly is cleaner and saves atoList()call. Looks good.lib/components/universal_navbar.dart (5)
36-39: LGTM: Using SizedBox for the bottom plant strip is appropriateThe swap to SizedBox tightens layout intent and avoids a decoration wrapper where none is needed.
154-161: LGTM: Solid colors and clear border improve contrastThe move to opaque beige and solid black border makes the strip crisper; decoration remains simple.
185-201: LGTM: Per-plant wrappers simplified to SizedBoxKeeps constraints explicit and reduces widget depth. Asset path handling looks consistent.
243-252: LGTM: Wallet menu surface colors updatedSolid white background, green border, and solid black shadow are fine; consistent with the rest of the visual refresh.
368-377: LGTM: Connect button surface consistencyWhite background + green border/shadow matches the wallet menu styling; good cohesion.
lib/widgets/nft_display_utils/user_nfts_widget.dart (1)
108-111: Constructor modernization toconst UserNftsWidget({super.key, ...})— LGTMMatches Flutter best practices and keeps API stable.
lib/pages/mint_nft/mint_nft_coordinates.dart (1)
9-10: Import path correction — LGTMRename aligns with the new widget location.
lib/pages/mint_nft/mint_nft_details.dart (2)
104-112: Gradient/shadow solid-color update — LGTMConsistent with the broader PR’s move away from opacity.
339-360: Preview chip solid color — LGTMGood contrast and consistent with the rest of the styling.
lib/widgets/map_widgets/flutter_map_widget.dart (5)
222-231: Switch to SizedBox for icon hit target is a nice cleanupUsing
SizedBox(40x40)keeps the touch target clear without extra decoration. LGTM.
253-262: Likewise for the minus button: LGTMConsistent touch target sizing and no unnecessary Container. Good.
307-310: Constructor modernization withsuper.keyand explicit fields looks goodPublic API stays the same while using idiomatic
super.key. No issues.
536-544: SizedBox touch target: LGTMGood consistency and accessibility for the zoom-in control.
567-575: SizedBox touch target: LGTMMatches the zoom-in pattern; clear and consistent.
.github/workflows/flutter.yaml
Outdated
| - name: Flutter build (web example) | ||
| run: flutter build web |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Heads-up: Web build may fail if any imported code uses dart:io. Verify import graph.
lib/utils/services/ipfs_services.dart imports dart:io. If it’s in the web import graph, flutter build web will fail.
Run this to confirm whether dart:io-importing files are in the web graph:
🏁 Script executed:
#!/bin/bash
# Find files importing dart:io
rg -nP -C2 "^\s*import\s+'dart:io';" lib || true
# Show who imports them (first level)
fd . lib -t f -e dart -x rg -nP -C1 "import .+ipfs_services\.dart" {}Length of output: 1262
🏁 Script executed:
#!/bin/bash
# Find who imports mint_nft_images.dart
rg -nP -C1 "import .+mint_nft_images\.dart" lib || true
# Find who imports register_user_page.dart
rg -nP -C1 "import .+register_user_page\.dart" lib || trueLength of output: 645
🏁 Script executed:
#!/bin/bash
# List all files importing ipfs_services.dart (show filenames and matched lines)
rg -nP --with-filename "import .*ipfs_services\.dart" libLength of output: 287
Action Required: Remove or Guard dart:io Imports to Enable Web Build
The import graph for your web example includes dart:io (which isn’t supported on the web), causing flutter build web to fail. Specifically:
lib/utils/services/ipfs_services.dartimportsdart:io.- That service is imported by:
lib/pages/register_user_page.dartlib/pages/mint_nft/mint_nft_images.dart
- Both pages are pulled in by the app’s entry point:
lib/main.dartimportsregister_user_page.dartandmint_nft_images.dart
Next steps:
• Refactor ipfs_services.dart to use conditional imports, splitting out dart:io–dependent code from a pure-Dart (web) implementation.
Example:
// lib/utils/services/ipfs_services.dart
export 'ipfs_services_mobile.dart' if (dart.library.html) 'ipfs_services_web.dart';• Create:
ipfs_services_mobile.dart– retains thedart:ioimports for mobile/desktopipfs_services_web.dart– useshttporjsinterop withoutdart:io
• Verify that neitherregister_user_page.dartnormint_nft_images.dartdirectly referencesdart:io; they should only depend on the abstracted service API.
Implementing these changes will allow flutter build web to succeed without removing your existing mobile/desktop functionality.
🤖 Prompt for AI Agents
.github/workflows/flutter.yaml around lines 34-35: the web build fails because
lib/utils/services/ipfs_services.dart imports dart:io (unsupported on web);
refactor ipfs_services.dart into a conditional-export facade and two
platform-specific implementations (one for mobile/desktop that keeps dart:io
usage, e.g., ipfs_services_mobile.dart, and one for web without dart:io, e.g.,
ipfs_services_web.dart using http/js interop), update the facade to use the
conditional export (export 'ipfs_services_mobile.dart' if (dart.library.html)
'ipfs_services_web.dart';), ensure register_user_page.dart and
mint_nft_images.dart import only the facade and do not directly import dart:io,
and run flutter build web to verify the issue is resolved.
| SizedBox( | ||
| width: 10, | ||
| child: Flexible( | ||
| child: Text( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Flexible cannot be a child of SizedBox (will throw “Incorrect use of ParentDataWidget”)
Flexible must be a direct descendant of a Flex (Row/Column). Nesting it inside SizedBox will cause a runtime error. Also, width: 10 is too narrow to display the formatted address.
Fix by removing SizedBox and letting Flexible handle truncation, or keep only SizedBox:
- SizedBox(
- width: 10,
- child: Flexible(
- child: Text(
+ Flexible(
+ child: Text(
formatAddress(walletProvider.currentAddress!),
style: TextStyle(
color: Colors.green[700],
fontWeight: FontWeight.w600,
fontSize: 10,
),
overflow: TextOverflow.ellipsis,
- ),
- ),
- ),
+ ),
+ ),If you want a fixed cap, you can alternatively use:
// Outside selected range (illustrative)
SizedBox(
width: 100,
child: Text(formatAddress(...), overflow: TextOverflow.ellipsis, style: ...),
)🤖 Prompt for AI Agents
In lib/components/universal_navbar.dart around lines 269 to 272, a Flexible is
nested inside a SizedBox (width: 10) which will throw "Incorrect use of
ParentDataWidget" because Flexible must be a direct child of a Flex and the
fixed width is far too small; fix by removing the SizedBox and using Flexible
(or Expanded) directly as a child of the surrounding Row/Column, ensure the Text
uses overflow: TextOverflow.ellipsis and maxLines: 1 so it truncates properly,
or if you need a fixed cap replace Flexible with a SizedBox with a sensible
width (e.g., ~100) and put the Text inside that SizedBox with overflow handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@IronJam11 Please look into this, check if there are any errors in running of this widget
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @CodeRabbit in a new comment, and I'll come hopping out!
| // ignore: use_build_context_synchronously | ||
| Navigator.of(context).pop(); | ||
| } catch (e) { | ||
| // ignore: use_build_context_synchronously | ||
| Navigator.of(context).pop(); | ||
| // ignore: use_build_context_synchronously | ||
| ScaffoldMessenger.of(context).showSnackBar( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
Don’t suppress use_build_context_synchronously; guard with context.mounted
Ignoring the lint risks using a disposed context after await. Check context.mounted (Flutter ≥3.7) before Navigator.pop and showing SnackBars.
Apply this diff:
onPressed: () async {
try {
await walletProvider.openWallet(wallet, uri);
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
} catch (e) {
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
- // ignore: use_build_context_synchronously
- ScaffoldMessenger.of(context).showSnackBar(
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
+ ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: Colors.red,
),
);
}
},
@@
onPressed: () async {
await Clipboard.setData(ClipboardData(text: uri));
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
- // ignore: use_build_context_synchronously
- ScaffoldMessenger.of(context).showSnackBar(
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
+ ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('URI copied to clipboard!'),
backgroundColor: Colors.green,
),
);
},If you support older Flutter without context.mounted, capture final nav = Navigator.of(context); final messenger = ScaffoldMessenger.of(context); before await and use those locals afterward.
Also applies to: 82-89
Guard against disposed BuildContext instead of suppressing the lint
Ignoring use_build_context_synchronously can lead to exceptions if the widget’s BuildContext is unmounted by the time an awaited call completes. Replace the // ignore: use_build_context_synchronously comments with proper guards:
• Affected locations:
– lib/components/wallet_connect_dialog.dart, onPressed handler (~lines 43–49)
– lib/components/wallet_connect_dialog.dart, URI-copy handler (~lines 82–89)
For Flutter ≥ 3.7 (with context.mounted):
onPressed: () async {
try {
await walletProvider.openWallet(wallet, uri);
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
+ // avoid using a disposed context
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
} catch (e) {
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
- // ignore: use_build_context_synchronously
- ScaffoldMessenger.of(context).showSnackBar(
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
+ ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: Colors.red,
),
);
}
},
…
onPressed: () async {
await Clipboard.setData(ClipboardData(text: uri));
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
- // ignore: use_build_context_synchronously
- ScaffoldMessenger.of(context).showSnackBar(
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
+ ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('URI copied to clipboard!'),
backgroundColor: Colors.green,
),
);
},Fallback for pre-3.7 Flutter (no context.mounted):
Capture NavigatorState and ScaffoldMessengerState before the await, then use those locals afterward:
onPressed: () async {
final nav = Navigator.of(context);
final messenger = ScaffoldMessenger.of(context);
try {
await walletProvider.openWallet(wallet, uri);
nav.pop();
} catch (e) {
nav.pop();
messenger.showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: Colors.red,
),
);
}
},
…
onPressed: () async {
final nav = Navigator.of(context);
final messenger = ScaffoldMessenger.of(context);
await Clipboard.setData(ClipboardData(text: uri));
nav.pop();
messenger.showSnackBar(
const SnackBar(
content: Text('URI copied to clipboard!'),
backgroundColor: Colors.green,
),
);
},🤖 Prompt for AI Agents
In lib/components/wallet_connect_dialog.dart around lines 43–49 and 82–89, the
code suppresses the use_build_context_synchronously lint instead of guarding
against a possibly disposed BuildContext; update both async handlers to avoid
using context after await by either (1) if using Flutter ≥3.7, check
context.mounted before calling Navigator.of(context).pop() and
ScaffoldMessenger.of(context).showSnackBar(...), or (2) for older Flutter,
capture NavigatorState and ScaffoldMessengerState into local variables before
any await (e.g., final nav = Navigator.of(context); final messenger =
ScaffoldMessenger.of(context);) and then use those locals after the await to
call nav.pop() and messenger.showSnackBar(...); remove the ignore comments and
ensure all code paths (success and catch) use the guarded approach so no context
is accessed when unmounted.
| import 'package:flutter_dotenv/flutter_dotenv.dart'; | ||
|
|
||
| final String ALCHEMY_API_KEY = dotenv.env['ALCHEMY_API_KEY'] ?? ''; | ||
| final String alchemyApiKey = dotenv.env['ALCHEMY_API_KEY'] ?? ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Top-level dotenv read at import time can initialize to empty before dotenv.load() runs.
Dart evaluates top-level initializers before main() (where dotenv.load() typically happens). This risks blank RPC URLs in production.
Apply this diff to make the API key lookup lazy (safe even if dotenv loads later):
-import 'package:flutter/material.dart';
-import 'package:flutter_dotenv/flutter_dotenv.dart';
-
-final String alchemyApiKey = dotenv.env['ALCHEMY_API_KEY'] ?? '';
+import 'package:flutter/material.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+
+String _alchemyApiKey() => dotenv.maybeGet('ALCHEMY_API_KEY') ?? '';And convert rpcUrls/chainInfoList to getters so the key is resolved at access time:
-final Map<String, String> rpcUrls = {
- '11155111': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey',
- '1': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey',
-};
+Map<String, String> get rpcUrls => {
+ '11155111': 'https://eth-sepolia.g.alchemy.com/v2/${_alchemyApiKey()}',
+ '1': 'https://eth-mainnet.g.alchemy.com/v2/${_alchemyApiKey()}',
+};
@@
-final Map<String, Map<String, dynamic>> chainInfoList = {
+Map<String, Map<String, dynamic>> get chainInfoList => {
'1': {
'name': 'Ethereum Mainnet',
- 'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey',
+ 'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/${_alchemyApiKey()}',
@@
'11155111': {
'name': 'Sepolia Testnet',
- 'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey',
+ 'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/${_alchemyApiKey()}',Optionally, fail fast in debug if the key is missing where you first consume these maps.
🤖 Prompt for AI Agents
In lib/models/wallet_chain_option.dart around line 4, the file reads
dotenv.env['ALCHEMY_API_KEY'] at import time which can evaluate to empty before
dotenv.load() runs; change the top-level alchemyApiKey to a getter that reads
dotenv.env['ALCHEMY_API_KEY'] on access, and convert rpcUrls and chainInfoList
to getters so they compute their maps using the runtime value of the API key;
update any code that depended on the old top-level fields to access the getters,
and optionally add a debug-only assertion or throw where these getters are first
consumed to fail fast if the key is missing.
| // ignore: use_build_context_synchronously | ||
| final provider = Provider.of<MintNftProvider>(context, listen: false); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Stop ignoring use_build_context_synchronously; capture provider before awaiting and add mounted guards
Using context after an await can crash if the widget unmounts meanwhile. Don’t suppress the lint; fix the flow by capturing the provider before the first await and guarding UI updates with mounted.
Apply this small in-range cleanup first:
- // ignore: use_build_context_synchronously
- final provider = Provider.of<MintNftProvider>(context, listen: false);
+ // Provider captured earlier (see revised function below).Then revise the function to safely capture the provider and avoid post-await context usage:
Future<void> _pickAndUploadImages() async {
// capture before any await
final provider = context.read<MintNftProvider>();
try {
final List<XFile> images = await _picker.pickMultiImage();
if (images.isEmpty) return;
if (!mounted) return;
setState(() {
_processingImages = images.map((image) => File(image.path)).toList();
_isUploading = true;
});
final List<String> newHashes = [];
for (int i = 0; i < images.length; i++) {
if (!mounted) return;
setState(() => _uploadingIndex = i);
try {
final File imageFile = File(images[i].path);
final String? hash = await uploadToIPFS(imageFile, (isUploading) {});
if (!mounted) return;
if (hash != null) {
newHashes.add(hash);
setState(() => _uploadedHashes.add(hash));
logger.d('Successfully uploaded image ${i + 1}: $hash');
} else {
_showSnackBar('Failed to upload image ${i + 1}');
}
} catch (e) {
logger.e('Error uploading image ${i + 1}: $e');
if (mounted) _showSnackBar('Error uploading image ${i + 1}: $e');
}
}
if (!mounted) return;
setState(() {
_isUploading = false;
_uploadingIndex = -1;
_processingImages.clear();
});
provider.setInitialPhotos(_uploadedHashes);
if (newHashes.isNotEmpty && mounted) {
_showSnackBar('Successfully uploaded ${newHashes.length} images');
}
} catch (e) {
if (!mounted) return;
setState(() {
_isUploading = false;
_uploadingIndex = -1;
_processingImages.clear();
});
if (mounted) _showSnackBar('Error selecting images: $e');
}
}Note: context.read() is equivalent to Provider.of(context, listen: false) but clearer.
🤖 Prompt for AI Agents
In lib/pages/mint_nft/mint_nft_images.dart around lines 46–48, the code
currently ignores the use_build_context_synchronously lint by calling
Provider.of<MintNftProvider>(context, listen: false) after an await; capture the
provider synchronously before any awaits and replace with
context.read<MintNftProvider>() (or store Provider.of(...) immediately), then
add mounted checks after each await before calling setState, updating widgets,
or using context (return early if !mounted); ensure all setState calls and UI
notifications (_showSnackBar, provider.setInitialPhotos, etc.) occur only when
mounted and reset _isUploading/_uploadingIndex/_processingImages in both success
and error paths.
| String apiKey = dotenv.get('API_KEY', fallback: ""); | ||
| String apiSecret = dotenv.get('API_SECRET', fallback: ""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Top-level dotenv reads will be empty if dotenv.load() runs after imports.
These variables initialize before main() and will stay "", breaking Pinata auth.
Apply this diff to compute keys at call-time (safe for both app and CI):
-import 'package:flutter_dotenv/flutter_dotenv.dart';
-
-String apiKey = dotenv.get('API_KEY', fallback: "");
-String apiSecret = dotenv.get('API_SECRET', fallback: "");
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+
+String get apiKey => dotenv.maybeGet('API_KEY') ?? '';
+String get apiSecret => dotenv.maybeGet('API_SECRET') ?? '';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| String apiKey = dotenv.get('API_KEY', fallback: ""); | |
| String apiSecret = dotenv.get('API_SECRET', fallback: ""); | |
| import 'package:flutter_dotenv/flutter_dotenv.dart'; | |
| String get apiKey => dotenv.maybeGet('API_KEY') ?? ''; | |
| String get apiSecret => dotenv.maybeGet('API_SECRET') ?? ''; |
🤖 Prompt for AI Agents
In lib/utils/services/ipfs_services.dart around lines 6 to 7, the top-level
reads dotenv.get('API_KEY') and dotenv.get('API_SECRET') run at import time and
will be empty if dotenv.load() runs later; change to read the env vars at
call-time (e.g., inside the functions that build Pinata auth headers or via a
small getter that calls dotenv.get on each invocation) so keys are retrieved
after dotenv.load(); also consider validating/throwing if values are still empty
to fail fast in CI.
| const CoordinatesMap( | ||
| {Key? key, | ||
| {super.key, | ||
| this.onLocationSelected, | ||
| required double lat, | ||
| required double lng}) | ||
| : super(key: key); | ||
| required double lng}); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove or use the unused required lat/lng in CoordinatesMap constructor
lat and lng are required positional params but not stored or used anywhere in CoordinatesMap. This creates a confusing public API and forces callers to pass values that are ignored.
Two safe options:
- Option A (prefer): Remove these params entirely if the widget is purely provider-driven.
- const CoordinatesMap(
- {super.key,
- this.onLocationSelected,
- required double lat,
- required double lng});
+ const CoordinatesMap({
+ super.key,
+ this.onLocationSelected,
+ });- Option B (fallback-friendly, non-breaking): Keep them optional and store as fields to use as a fallback if provider yields invalid coordinates.
class CoordinatesMap extends StatefulWidget {
final Function(double lat, double lng)? onLocationSelected;
+ final double? lat;
+ final double? lng;
- const CoordinatesMap(
- {super.key,
- this.onLocationSelected,
- required double lat,
- required double lng});
+ const CoordinatesMap({
+ super.key,
+ this.onLocationSelected,
+ this.lat,
+ this.lng,
+ });If you choose Option B, I can propose a small update in build() to fall back to widget.lat/lng when provider returns NaN/∞. Want me to wire that up?
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const CoordinatesMap( | |
| {Key? key, | |
| {super.key, | |
| this.onLocationSelected, | |
| required double lat, | |
| required double lng}) | |
| : super(key: key); | |
| required double lng}); | |
| const CoordinatesMap({ | |
| super.key, | |
| this.onLocationSelected, | |
| }); |
🤖 Prompt for AI Agents
In lib/widgets/map_widgets/flutter_map_widget.dart around lines 11–16, the
constructor declares required double lat and required double lng but never
stores or uses them; remove these unused parameters from the public API
(preferred) by deleting them from the constructor and updating any callers, or
if you need non-breaking fallback behavior, convert them to nullable final
fields (final double? lat, final double? lng), make the constructor accept
optional named params and assign them, then update build() to use
widget.lat/widget.lng as a fallback when the provider returns invalid
coordinates (NaN/∞); also update any documentation/comments and unit tests/call
sites accordingly.
| // ignore: unused_field | ||
| int _currentPage = 0; | ||
| // ignore: unused_field | ||
| final int _itemsPerPage = 10; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove incorrect unused_field ignores and actually use pagination state
_currentPage is used below; _itemsPerPage should be used to drive the paginated fetch. Drop the ignores and wire these fields into the API call.
Apply this diff:
- // ignore: unused_field
int _currentPage = 0;
- // ignore: unused_field
- final int _itemsPerPage = 10;
+ final int _itemsPerPage = 10;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // ignore: unused_field | |
| int _currentPage = 0; | |
| // ignore: unused_field | |
| final int _itemsPerPage = 10; | |
| int _currentPage = 0; | |
| final int _itemsPerPage = 10; |
🤖 Prompt for AI Agents
In lib/widgets/nft_display_utils/user_nfts_widget.dart around lines 121 to 124,
remove the incorrect // ignore: unused_field comments and wire the pagination
state into the API call: stop ignoring _currentPage and _itemsPerPage, use
_itemsPerPage as the page size when requesting data (pass it as limit/size or
per_page parameter) and compute the offset or page index from _currentPage
(e.g., offset = _currentPage * _itemsPerPage or pass _currentPage directly if
API expects page number). Also ensure any UI pagination controls
increment/decrement _currentPage inside setState and trigger a refetch using the
updated page and _itemsPerPage values so pagination actually drives the
requests.
| name: actualData[2].toString(), | ||
| userAddress: actualData[0].toString(), | ||
| profilePhotoIpfs: actualData[1].toString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid "null" showing up in UI and malformed image URLs — restore null coalescing
toString() on null yields the literal "null", which will (a) display as a name "null" and (b) try to load an image from "null". Use null-aware defaults.
- name: actualData[2].toString(),
- userAddress: actualData[0].toString(),
- profilePhotoIpfs: actualData[1].toString(),
+ name: (actualData[2] == null) ? '' : actualData[2].toString(),
+ userAddress: (actualData[0] == null) ? '' : actualData[0].toString(),
+ profilePhotoIpfs: (actualData[1] == null) ? '' : actualData[1].toString(),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| name: actualData[2].toString(), | |
| userAddress: actualData[0].toString(), | |
| profilePhotoIpfs: actualData[1].toString(), | |
| name: (actualData[2] == null) ? '' : actualData[2].toString(), | |
| userAddress: (actualData[0] == null) ? '' : actualData[0].toString(), | |
| profilePhotoIpfs: (actualData[1] == null) ? '' : actualData[1].toString(), |
🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/profile_section_widget.dart around lines 37 to
39, the code calls toString() directly on possibly-null actualData elements
which produces the literal "null" (showing in UI and creating malformed image
URLs); replace those with null-aware expressions such as
actualData[2]?.toString() ?? '' for name, actualData[0]?.toString() ?? '' for
userAddress, and actualData[1]?.toString() ?? '' (or null/empty as appropriate
for profilePhotoIpfs) so that nulls are coalesced to safe defaults and no "null"
string is rendered or used as an image URL.
| ); | ||
| } | ||
|
|
||
| // ignore: unused_element |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t suppress unused error-view — actually show it to avoid a crash when profile load fails
Right now, on any non-“not registered” error, build still renders the profile/tiles, dereferencing _userProfileData! and risking a runtime crash. Use _buildErrorState() instead of suppressing it.
Minimal integration (outside selected range):
// In build(), replace the ternary with an error-aware branch
child: _isLoading
? _buildLoadingState()
: _isNotRegistered
? _buildNotRegisteredState()
: (_errorMessage != null && _errorMessage!.isNotEmpty)
? _buildErrorState()
: Row(
// current overview + tokens
),Once used, remove the // ignore: unused_element.
🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/profile_section_widget.dart around line 358, the
code currently suppresses an unused-element warning and still renders the
profile UI even when a non-"not registered" load error occurred, which
dereferences _userProfileData! and can crash; update the build logic to check
_errorMessage (or equivalent error flag) and call _buildErrorState() when an
error exists instead of rendering the profile overview, ensure the profile
widgets only render when _userProfileData is non-null, and remove the
now-unnecessary “// ignore: unused_element” comment for the unused error-view
helper.
9f81283 to
1cd1feb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/widgets/map_widgets/flutter_map_widget.dart (1)
105-109: Fix invalid initialZoom/minZoom combination
initialZoom: 1.0whileminZoom: 3.0is contradictory; the map will clamp or behave unexpectedly on first paint. MakeinitialZoom >= minZoom(or lowerminZoom).- initialZoom: 1.0, - minZoom: 3.0, + initialZoom: 3.0, + minZoom: 3.0,
♻️ Duplicate comments (13)
.env.stencil (1)
6-6: Ensure APPLICATION_ID is actually used and documented (duplicate from earlier review).APPLICATION_ID is templated but I can’t confirm it’s read at runtime or mentioned in README/Getting Started.
Run this to verify usage and docs:
#!/bin/bash # Search app code for APPLICATION_ID access and env loading rg -nCI3 --glob '!**/build/**' \ -e '\bAPPLICATION_ID\b' \ -e 'flutter_dotenv|dotenv|--dart-define' # Check README for onboarding instructions rg -n -C2 'APPLICATION_ID|\.env' README.md || trueIf unused, remove it from .env.stencil; if used, document: copy .env.stencil -> .env and set APPLICATION_ID.
.github/workflows/flutter.yaml (2)
15-16: Upgrade checkout to v4 for current runners and Node 20 compatibility.Apply:
- - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0
28-33: Add a Flutter test step to CI.You already analyze and format; run tests to catch regressions.
Apply:
- name: Dart analyze run: flutter analyze + - name: Run Flutter tests + run: flutter test -r expanded + - name: Check formatting run: dart format --output=none --set-exit-if-changed .lib/utils/services/contract_read_services.dart (4)
1-5: Add missing web3dart import for EthereumAddress (compile error).
EthereumAddressis defined in web3dart, not in walletconnect_flutter_v2. This file won’t compile without the import.import 'package:tree_planting_protocol/utils/constants/contract_abis/tree_nft_contract_abi.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; +import 'package:web3dart/credentials.dart';
70-76: Guard against unset/malformed contract address and use consistent casing.Add a pre-call check for the contract address and standardize the identifier to camelCase
treeNftContractAddress. This prevents confusing runtime failures when the env value is missing/invalid. Also aligns with Dart naming style.final List<dynamic> args = [ userAddress, BigInt.from(offset), BigInt.from(limit), ]; - final result = await walletProvider.readContract( - contractAddress: treeNFtContractAddress, + // Validate configured contract address + if (treeNftContractAddress.isEmpty || + !RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(treeNftContractAddress)) { + return ContractReadResult.error( + errorMessage: + 'Invalid contract address configured. Set TREE_NFT_CONTRACT_ADDRESS.', + ); + } + final result = await walletProvider.readContract( + contractAddress: treeNftContractAddress, functionName: 'getNFTsByUserPaginated', params: args, abi: treeNftContractABI, );If the source constant is currently named
treeNFtContractAddress, please rename it at the declaration site too for consistency and update all callsites.
118-123: Repeat: validate contract address and fix identifier casing in ping().- final result = await walletProvider.readContract( - contractAddress: treeNFtContractAddress, + if (treeNftContractAddress.isEmpty || + !RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(treeNftContractAddress)) { + return ContractReadResult.error( + errorMessage: + 'Invalid contract address configured. Set TREE_NFT_CONTRACT_ADDRESS.', + ); + } + final result = await walletProvider.readContract( + contractAddress: treeNftContractAddress, functionName: 'ping', - abi: treeNftContractABI, + abi: treeNftContractABI, params: [], );
174-178: Validate contract address and standardize casing in getProfileDetails().- final result = await walletProvider.readContract( - contractAddress: treeNFtContractAddress, + if (treeNftContractAddress.isEmpty || + !RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(treeNftContractAddress)) { + return ContractReadResult.error( + errorMessage: + 'Invalid contract address configured. Set TREE_NFT_CONTRACT_ADDRESS.', + ); + } + final result = await walletProvider.readContract( + contractAddress: treeNftContractAddress, functionName: 'getUserProfile', abi: treeNftContractABI, params: args, );lib/models/wallet_chain_option.dart (3)
37-58: Same issue for chainInfoList: compute lazily to avoid blank RPC URLs.-final Map<String, Map<String, dynamic>> chainInfoList = { +Map<String, Map<String, dynamic>> get chainInfoList => { '1': { 'name': 'Ethereum Mainnet', - 'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey', + 'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/${_alchemyApiKey()}', 'nativeCurrency': { 'name': 'Ether', 'symbol': 'ETH', 'decimals': 18, }, 'blockExplorerUrl': 'https://etherscan.io', }, '11155111': { 'name': 'Sepolia Testnet', - 'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey', + 'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/${_alchemyApiKey()}', 'nativeCurrency': { 'name': 'Sepolia Ether', 'symbol': 'SEP', 'decimals': 18, }, 'blockExplorerUrl': 'https://sepolia.etherscan.io', }, };Optionally, assert/log when the key is missing at first use to fail fast in debug:
- assert(_alchemyApiKey().isNotEmpty, 'Missing ALCHEMY_API_KEY');
4-4: Don’t read dotenv at import time; make API key lookup lazy.Top-level
finalinitializes beforedotenv.load()runs, leading to permanently blank RPC URLs. Compute the key at access time.-final String alchemyApiKey = dotenv.env['ALCHEMY_API_KEY'] ?? ''; +String _alchemyApiKey() => dotenv.maybeGet('ALCHEMY_API_KEY') ?? '';
32-35: Convert rpcUrls to a getter so it uses the runtime API key.-final Map<String, String> rpcUrls = { - '11155111': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey', - '1': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey', -}; +Map<String, String> get rpcUrls => { + '11155111': 'https://eth-sepolia.g.alchemy.com/v2/${_alchemyApiKey()}', + '1': 'https://eth-mainnet.g.alchemy.com/v2/${_alchemyApiKey()}', +};lib/widgets/profile_widgets/profile_section_widget.dart (2)
37-39: Prevent "null" leaking into UI — restore null-aware defaultsCalling toString() on null yields the literal "null", which will render in the name and attempt to fetch an image from "null". Use null-aware coalescing.
- name: actualData[2].toString(), - userAddress: actualData[0].toString(), - profilePhotoIpfs: actualData[1].toString(), + name: actualData[2]?.toString() ?? '', + userAddress: actualData[0]?.toString() ?? '', + profilePhotoIpfs: actualData[1]?.toString() ?? '',Optional: if profilePhotoIpfs can be "ipfs://...", normalize to a gateway before passing to Image.network.
358-358: Don’t suppress the error view; render it when a load error occursCurrently, build() ignores _errorMessage and continues to render widgets that dereference _userProfileData!, risking a crash on non-“not registered” failures. Use _buildErrorState() and remove the suppression.
- // ignore: unused_elementApply these supporting changes outside the selected range:
- Update build() to branch on error and guard null profile:
@override Widget build(BuildContext context) { final hasError = _errorMessage != null && _errorMessage!.isNotEmpty; final profile = _userProfileData; return Container( padding: const EdgeInsets.all(16), child: _isLoading ? _buildLoadingState() : _isNotRegistered ? _buildNotRegisteredState() : hasError ? _buildErrorState() : (profile == null) ? _buildLoadingState() : Row( mainAxisAlignment: MainAxisAlignment.start, children: [ _profileOverview(), const SizedBox(width: 15), _tokenWidget(), ], ), ); }
- Normalize error state variable usage and fix typo:
// Prefer: null means "no error", any non-null string is an error message String? _errorMessage; // ... setState(() { _errorMessage = 'Error loading User profile details: $e'; });lib/widgets/map_widgets/flutter_map_widget.dart (1)
12-15: Remove or use the unused required lat/lng in CoordinatesMap constructor
latandlngare still required but never stored or used. This forces callers to pass meaningless values and makes the API misleading. This was flagged earlier and remains unresolved.Option A (preferred): remove the params.
- const CoordinatesMap( - {super.key, - this.onLocationSelected, - required double lat, - required double lng}); + const CoordinatesMap({ + super.key, + this.onLocationSelected, + });Option B (fallback, non-breaking): add nullable fields and use as fallback when provider emits invalid coordinates.
class CoordinatesMap extends StatefulWidget { final Function(double lat, double lng)? onLocationSelected; + final double? lat; + final double? lng; - const CoordinatesMap( - {super.key, - this.onLocationSelected, - required double lat, - required double lng}); + const CoordinatesMap({ + super.key, + this.onLocationSelected, + this.lat, + this.lng, + });If you choose Option B, I’ll wire up the fallback in build() to use
widget.lat/widget.lngwhen provider returns invalid values.Run this to find and update all call sites:
#!/bin/bash # Find all usages of CoordinatesMap(...) and show surrounding context. rg -nP --type=dart -C2 '\bCoordinatesMap\s*\('
🧹 Nitpick comments (19)
.env.stencil (1)
1-6: Reorder keys and add trailing newline to satisfy dotenv-linter; keep template tidy.Current ordering triggers UnorderedKey warnings and there is no ending blank line. Sort keys alphabetically (common practice) and add a newline.
Apply:
-WALLETCONNECT_PROJECT_ID=<WALLETCONNECT_PROJECT_ID> -API_KEY=<PINATA_API_KEY> -API_SECRET=<PINATA_API_SECRET> -ALCHEMY_API_KEY=<ALCHEMY_API_KEY> -CONTRACT_ADDRESS=<CONTRACT_ADDRESS> -APPLICATION_ID=<APPLICATION_ID> +ALCHEMY_API_KEY=<ALCHEMY_API_KEY> +API_KEY=<PINATA_API_KEY> +API_SECRET=<PINATA_API_SECRET> +APPLICATION_ID=<APPLICATION_ID> +CONTRACT_ADDRESS=<CONTRACT_ADDRESS> +WALLETCONNECT_PROJECT_ID=<WALLETCONNECT_PROJECT_ID> +.github/workflows/flutter.yaml (2)
17-23: Trim trailing spaces and enable Flutter SDK/pub caching.There are trailing spaces on Lines 17, 18, and 23. Also, enabling caching speeds up CI.
Apply:
- - name: Setup environment file from template - run: cp .env.stencil .env + - name: Setup environment file from template + run: cp .env.stencil .env - name: Setup Flutter uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: 'stable' + cache: true
20-26: Verify Android toolchain availability for APK builds; pin JDK if needed.ubuntu-latest has a JDK, but the Android SDK/platforms may be missing. Confirm build reliability; if flaky, set up Java 17 and Android SDK explicitly.
Option to pin Java and install SDK:
- name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: 'stable' cache: true + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + - name: Set up Android SDK + uses: android-actions/setup-android@v3 + + - name: Accept Android licenses + run: yes | sdkmanager --licensesThen ensure required platforms/build-tools are present (adjust versions to your minSdk/targetSdk):
- name: Install Android platforms/build-tools run: | sdkmanager "platforms;android-34" "build-tools;34.0.0" "platform-tools"Also applies to: 34-35
lib/utils/services/contract_read_services.dart (3)
50-56: Strengthen wallet address validation.
startsWith('0x')is too weak. Validate full EIP-55 shape to avoid passing malformed addresses downstream.- if (!address.startsWith('0x')) { + if (!RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(address)) { return ContractReadResult.error( errorMessage: 'Invalid wallet address format', ); }
164-168: Strengthen wallet address validation here as well.- if (!address.startsWith('0x')) { + if (!RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(address)) { return ContractReadResult.error( errorMessage: 'Invalid wallet address format', ); }
170-173: Avoid null-assert on currentAddress; fail fast with a clear error.
walletProvider.currentAddress!may be null even ifisConnectedis true (depends on provider state). Guard explicitly.- final String currentAddress = walletProvider.currentAddress!.toString(); + final current = walletProvider.currentAddress; + if (current == null) { + return ContractReadResult.error( + errorMessage: 'Wallet address unavailable. Please reconnect.', + ); + } + final String currentAddress = current.toString();lib/widgets/profile_widgets/profile_section_widget.dart (1)
49-51: Log parse failures at error level and keep stack tracesThis is an error path; emit logger.e with stack trace so issues don’t get lost in debug-only logs.
- logger.d("Error parsing Tree data: $e"); - logger.d("Data received: $data"); - logger.d("Data type: ${data.runtimeType}"); + // Prefer capturing full context at error level + // ignore: avoid_catches_without_on_clauses + logger.e("Error parsing Tree data", error: e, stackTrace: StackTrace.current); + logger.d("Data received: $data"); + logger.d("Data type: ${data.runtimeType}");lib/widgets/map_widgets/flutter_map_widget.dart (12)
129-138: Log tile errors to aid troubleshooting; keep error UI as-isRight now, failures silently flip
_hasErrorwith no diagnostics. Add a log for tile failures; it helps distinguish network vs. template/config issues.errorTileCallback: (tile, error, stackTrace) { if (mounted) { + logger.e('Tile load error: $error', stackTrace); setState(() { _hasError = true; }); } },
157-167: Prefer theme-based loading overlay colors for Dark Mode/accessibilityHardcoded white can clash in dark themes. Use colorScheme.surface and onSurface instead.
- Container( - color: Colors.white, - child: const Center( + Container( + color: Theme.of(context).colorScheme.surface, + child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - CircularProgressIndicator(), + const CircularProgressIndicator(), SizedBox(height: 16), - Text("Loading map..."), + Text( + "Loading map...", + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ), ], ), ), ),
173-181: Use theme tokens for the coordinate badge background/foregroundSolid black/white ignores theme; invertible surfaces read better in both schemes.
- decoration: BoxDecoration( - color: Colors.black, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.inverseSurface, borderRadius: BorderRadius.circular(4), ), child: Text( "${latitude.toStringAsFixed(6)}, ${longitude.toStringAsFixed(6)}", - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: Theme.of(context).colorScheme.onInverseSurface, fontSize: 10, ), ),
192-201: Tweak shadow color for better elevation semanticsA pure black shadow is visually heavy. Prefer a translucent black or theme.shadowColor for Material-like elevation.
- BoxShadow( - color: Colors.black, + BoxShadow( + color: Colors.black26, blurRadius: 4, offset: const Offset(0, 2), ),
277-289: Theme the bottom info bar instead of hardcoding blueHardcoding can clash with brand/dark mode. Use
colorScheme.primaryandonPrimary.- decoration: BoxDecoration( - color: Colors.blue, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(4), ), - child: const Text( + child: Text( "Tap to set location • Use zoom buttons or pinch to zoom", - style: TextStyle( - color: Colors.white, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, fontSize: 11, ), textAlign: TextAlign.center, ),
329-339: Nice defensive sanitize helper; consider sharing it with CoordinatesMapThis helper is solid and mirrors what CoordinatesMap needs. To avoid duplication, extract a shared private top-level function or a small utility.
For example, at top-level in this file:
double sanitizeCoordinate(double value, double defaultValue) { if (value.isNaN || !value.isFinite) return defaultValue; return value; }Then call it from both widgets.
407-416: Avoid duplicate coordinate checks; reuse the sanitizerThis re-check duplicates the earlier sanitize/clamp in build(). Prefer calling the sanitizer here (or only in one place) to keep logic single-sourced.
- if (latitude.isNaN || - latitude.isInfinite || - longitude.isNaN || - longitude.isInfinite) { - logger.e( - 'ERROR: Invalid coordinates in _buildMapWidget - lat: $latitude, lng: $longitude'); - latitude = _defaultLat; - longitude = _defaultLng; - } + latitude = _sanitizeCoordinate(latitude, _defaultLat); + longitude = _sanitizeCoordinate(longitude, _defaultLng);
436-439: Confirm “static” behavior: should StaticDisplayMap fire onTap and allow edits?StaticDisplayMap currently invokes
onLocationSelectedon tap and allows all interactions (panning/zoom). If the intent is truly static, disable tap or reduce interaction flags; otherwise, consider renaming to avoid confusion.Option (static):
- interactionOptions: const InteractionOptions( - flags: InteractiveFlag.all, - ), + interactionOptions: const InteractionOptions( + flags: InteractiveFlag.doubleTapZoom | InteractiveFlag.pinchZoom, + ), - onTap: (tapPosition, point) { - if (widget.onLocationSelected != null) { - widget.onLocationSelected!(point.latitude, point.longitude); - } - }, + // No onTap for purely static display
471-482: Theme-based loading overlay for StaticDisplayMapSame note as CoordinatesMap: use surface/onSurface to respect theme and improve readability.
- Container( - color: Colors.white, - child: const Center( + Container( + color: Theme.of(context).colorScheme.surface, + child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - CircularProgressIndicator(), + const CircularProgressIndicator(), SizedBox(height: 16), - Text("Loading map..."), + Text( + "Loading map...", + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ), ], ), ), ),
489-498: Theme the coordinate badge in StaticDisplayMapMirror the theming suggestion made for CoordinatesMap for consistency across widgets.
- decoration: BoxDecoration( - color: Colors.black, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.inverseSurface, borderRadius: BorderRadius.circular(4), ), child: Text( "${latitude.toStringAsFixed(6)}, ${longitude.toStringAsFixed(6)}", - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: Theme.of(context).colorScheme.onInverseSurface, fontSize: 10, ), ),
512-515: Lighter shadow color for consistencySame rationale as earlier shadow: use translucent black or theme.shadowColor.
- BoxShadow( - color: Colors.black, + BoxShadow( + color: Colors.black26, blurRadius: 4, offset: const Offset(0, 2), ),
591-603: Theme the bottom info bar (StaticDisplayMap)Mirror the suggestion from CoordinatesMap for primary/onPrimary.
- decoration: BoxDecoration( - color: Colors.blue, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(4), ), - child: const Text( + child: Text( "Static display • Use zoom buttons or pinch to zoom", - style: TextStyle( - color: Colors.white, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, fontSize: 11, ), textAlign: TextAlign.center, ),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (24)
.env.stencil(1 hunks).github/workflows/flutter.yaml(1 hunks)lib/components/universal_navbar.dart(7 hunks)lib/components/wallet_connect_dialog.dart(2 hunks)lib/models/wallet_chain_option.dart(3 hunks)lib/pages/home_page.dart(0 hunks)lib/pages/mint_nft/mint_nft_coordinates.dart(2 hunks)lib/pages/mint_nft/mint_nft_details.dart(8 hunks)lib/pages/mint_nft/mint_nft_images.dart(2 hunks)lib/pages/register_user_page.dart(4 hunks)lib/providers/mint_nft_provider.dart(0 hunks)lib/providers/wallet_provider.dart(1 hunks)lib/utils/constants/contractDetails.dart(0 hunks)lib/utils/constants/contract_abis/tree_nft_contract_abi.dart(2 hunks)lib/utils/services/contract_read_services.dart(4 hunks)lib/utils/services/contract_write_functions.dart(2 hunks)lib/utils/services/ipfs_services.dart(2 hunks)lib/utils/services/switch_chain_utils.dart(1 hunks)lib/widgets/map_widgets/flutter_map_widget.dart(15 hunks)lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart(2 hunks)lib/widgets/nft_display_utils/tree_nft_view_widget.dart(1 hunks)lib/widgets/nft_display_utils/user_nfts_widget.dart(4 hunks)lib/widgets/profile_widgets/profile_section_widget.dart(3 hunks)lib/widgets/wallet_not_connected_widget.dart(1 hunks)
💤 Files with no reviewable changes (3)
- lib/utils/constants/contractDetails.dart
- lib/providers/mint_nft_provider.dart
- lib/pages/home_page.dart
✅ Files skipped from review due to trivial changes (2)
- lib/widgets/nft_display_utils/tree_nft_view_widget.dart
- lib/providers/wallet_provider.dart
🚧 Files skipped from review as they are similar to previous changes (13)
- lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart
- lib/utils/services/ipfs_services.dart
- lib/pages/mint_nft/mint_nft_images.dart
- lib/utils/constants/contract_abis/tree_nft_contract_abi.dart
- lib/utils/services/switch_chain_utils.dart
- lib/widgets/nft_display_utils/user_nfts_widget.dart
- lib/widgets/wallet_not_connected_widget.dart
- lib/pages/register_user_page.dart
- lib/utils/services/contract_write_functions.dart
- lib/pages/mint_nft/mint_nft_coordinates.dart
- lib/components/universal_navbar.dart
- lib/components/wallet_connect_dialog.dart
- lib/pages/mint_nft/mint_nft_details.dart
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/flutter.yaml
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 YAMLlint (1.37.1)
.github/workflows/flutter.yaml
[error] 17-17: trailing spaces
(trailing-spaces)
[error] 18-18: trailing spaces
(trailing-spaces)
[error] 23-23: trailing spaces
(trailing-spaces)
[warning] 35-35: too many spaces after colon
(colons)
🪛 dotenv-linter (3.3.0)
.env.stencil
[warning] 5-5: [UnorderedKey] The CONTRACT_ADDRESS key should go before the WALLETCONNECT_PROJECT_ID key
(UnorderedKey)
[warning] 6-6: [EndingBlankLine] No blank line at the end of the file
(EndingBlankLine)
[warning] 6-6: [UnorderedKey] The APPLICATION_ID key should go before the CONTRACT_ADDRESS key
(UnorderedKey)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (6)
lib/utils/services/contract_read_services.dart (1)
1-189: All instances oftreeNFtContractAddressare consistentThe ripgrep scan returned exactly six matches across the codebase, all of which refer to the same constant definition and its uses in contract read/write services and the ABI file. There are no stray or mixed-case identifiers elsewhere.
If you decide to rename
treeNFtContractAddresstotreeNftContractAddress, remember to update the following locations:• lib/utils/constants/contract_abis/tree_nft_contract_abi.dart (definition)
• lib/utils/services/contract_read_services.dart (3 usages)
• lib/utils/services/contract_write_functions.dart (2 usages)lib/widgets/map_widgets/flutter_map_widget.dart (5)
222-231: LGTM: Increased tap target for zoom-inSwitching to a 40x40 SizedBox improves accessibility and consistency with Material minimums.
253-262: LGTM: Increased tap target for zoom-outSame improvement as zoom-in; good call for usability.
307-310: Good: Constructor modernization with super.key and explicit fieldsThe StaticDisplayMap API is clear: explicit lat/lng and optional callback. This aligns with Flutter conventions.
536-545: LGTM: 40x40 tap target for zoom-in (StaticDisplayMap)Consistent accessibility improvement.
567-575: LGTM: 40x40 tap target for zoom-out (StaticDisplayMap)Consistent accessibility improvement.
.github/workflows/flutter.yaml
Outdated
| - name: Flutter build (apk) | ||
| run: flutter build apk --release --no-tree-shake-icons 2>/dev/null | grep -v "deprecated" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not pipe build output to grep; this can hide failures or cause false negatives.
Piping to grep makes the step’s exit code depend on grep, not flutter, and can fail when there’s no stdout even if the build succeeded. It can also mask real errors by discarding stderr.
Apply:
- - name: Flutter build (apk)
- run: flutter build apk --release --no-tree-shake-icons 2>/dev/null | grep -v "deprecated"
+ - name: Flutter build (apk)
+ run: flutter build apk --release --no-tree-shake-iconsIf you truly need to filter noisy lines without losing the build’s exit code, use pipefail and tee:
- name: Flutter build (apk)
run: |
set -o pipefail
flutter build apk --release --no-tree-shake-icons 2>&1 | tee build.log🧰 Tools
🪛 YAMLlint (1.37.1)
[warning] 35-35: too many spaces after colon
(colons)
🤖 Prompt for AI Agents
.github/workflows/flutter.yaml lines 34-35: the workflow step pipes flutter
build output to grep which can mask build failures and makes the step exit code
reflect grep instead of flutter; replace the single-line piped command with a
multi-line run that enables pipefail, captures both stdout and stderr, and
writes output to a log (so noisy lines can be inspected) while preserving the
flutter command's exit status — specifically set pipefail before running
flutter, redirect stderr to stdout, and tee the output to a file instead of
piping to grep.
| final double latitude = provider.getLatitude(); | ||
| final double longitude = provider.getLongitude(); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add coordinate sanitization/clamping before rendering
Unlike StaticDisplayMap, CoordinatesMap passes provider values straight to the map. NaN/∞/out-of-range values can crash or mis-center the map. Mirror the StaticDisplayMap guards.
Proposed minimal patch:
- final double latitude = provider.getLatitude();
- final double longitude = provider.getLongitude();
+ final double rawLat = provider.getLatitude();
+ final double rawLng = provider.getLongitude();
+ final double latitude = _sanitizeCoordinate(rawLat, 0.0).clamp(-90.0, 90.0).toDouble();
+ final double longitude = _sanitizeCoordinate(rawLng, 0.0).clamp(-180.0, 180.0).toDouble();Add this helper inside _CoordinatesMapState (same as in StaticDisplayMap), optionally logging when coercing:
class _CoordinatesMapState extends State<CoordinatesMap> {
late MapController _mapController;
bool _mapLoaded = false;
bool _hasError = false;
+ double _sanitizeCoordinate(double value, double defaultValue) {
+ if (value.isNaN || !value.isFinite) {
+ logger.e('Invalid coordinate detected: $value, using default: $defaultValue');
+ return defaultValue;
+ }
+ return value;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| final double latitude = provider.getLatitude(); | |
| final double longitude = provider.getLongitude(); | |
| ++ b/lib/widgets/map_widgets/flutter_map_widget.dart | |
| @@ build(BuildContext context) { | |
| - final double latitude = provider.getLatitude(); | |
| final double rawLat = provider.getLatitude(); | |
| final double rawLng = provider.getLongitude(); | |
| final double latitude = _sanitizeCoordinate(rawLat, 0.0) | |
| .clamp(-90.0, 90.0) | |
| .toDouble(); | |
| final double longitude = _sanitizeCoordinate(rawLng, 0.0) | |
| .clamp(-180.0, 180.0) | |
| .toDouble(); |
| final double latitude = provider.getLatitude(); | |
| final double longitude = provider.getLongitude(); | |
| ++ b/lib/widgets/map_widgets/flutter_map_widget.dart | |
| @@ class _CoordinatesMapState extends State<CoordinatesMap> { | |
| late MapController _mapController; | |
| bool _mapLoaded = false; | |
| bool _hasError = false; | |
| double _sanitizeCoordinate(double value, double defaultValue) { | |
| if (value.isNaN || !value.isFinite) { | |
| logger.e('Invalid coordinate detected: $value, using default: $defaultValue'); | |
| return defaultValue; | |
| } | |
| return value; | |
| } |
| Widget _tokenWidget() { | ||
| return Container( | ||
| child: Column( | ||
| mainAxisAlignment: MainAxisAlignment.start, | ||
| children: [ | ||
| Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: SizedBox( | ||
| height: 40, | ||
| width: 150, | ||
| child: Container( | ||
| decoration: BoxDecoration( | ||
| color: const Color.fromARGB(255, 251, 251, 99), | ||
| border: Border.all( | ||
| color: Colors.black, | ||
| width: 2, | ||
| ), | ||
| borderRadius: BorderRadius.circular(12), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black12, // shadow color | ||
| blurRadius: 6, // shadow softness | ||
| offset: Offset(0, 3), // shadow position | ||
| ), | ||
| ], | ||
| return Column( | ||
| mainAxisAlignment: MainAxisAlignment.start, | ||
| children: [ | ||
| Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: SizedBox( | ||
| height: 40, | ||
| width: 150, | ||
| child: Container( | ||
| decoration: BoxDecoration( | ||
| color: const Color.fromARGB(255, 251, 251, 99), | ||
| border: Border.all( | ||
| color: Colors.black, | ||
| width: 2, | ||
| ), | ||
| child: Center( | ||
| child: Text( | ||
| 'Planter Tokens : ${_userProfileData!.planterTokens}'))), | ||
| ), | ||
| ), | ||
| Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: SizedBox( | ||
| height: 40, | ||
| width: 150, | ||
| child: Container( | ||
| decoration: BoxDecoration( | ||
| color: const Color.fromARGB(255, 28, 211, 129), | ||
| border: Border.all( | ||
| color: Colors.black, | ||
| width: 2, | ||
| borderRadius: BorderRadius.circular(12), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black12, // shadow color | ||
| blurRadius: 6, // shadow softness | ||
| offset: Offset(0, 3), // shadow position | ||
| ), | ||
| borderRadius: BorderRadius.circular(12), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black12, | ||
| blurRadius: 6, | ||
| offset: Offset(0, 3), | ||
| ), | ||
| ], | ||
| ), | ||
| child: Center( | ||
| child: Text( | ||
| 'Care Tokens : ${_userProfileData!.careTokens}'))), | ||
| ), | ||
| ], | ||
| ), | ||
| child: Center( | ||
| child: Text( | ||
| 'Planter Tokens : ${_userProfileData!.planterTokens}'))), | ||
| ), | ||
| Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: SizedBox( | ||
| height: 40, | ||
| width: 150, | ||
| child: Container( | ||
| decoration: BoxDecoration( | ||
| color: const Color.fromARGB(255, 251, 251, 99), | ||
| border: Border.all( | ||
| color: Colors.black, | ||
| width: 2, | ||
| ), | ||
| borderRadius: BorderRadius.circular(12), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black12, | ||
| blurRadius: 6, | ||
| offset: Offset(0, 3), | ||
| ), | ||
| ], | ||
| ), | ||
| Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: SizedBox( | ||
| height: 40, | ||
| width: 150, | ||
| child: Container( | ||
| decoration: BoxDecoration( | ||
| color: const Color.fromARGB(255, 28, 211, 129), | ||
| border: Border.all( | ||
| color: Colors.black, | ||
| width: 2, | ||
| ), | ||
| child: Center( | ||
| child: Text( | ||
| 'Verifier Tokens : ${_userProfileData!.verifierTokens}'))), | ||
| ), | ||
| borderRadius: BorderRadius.circular(12), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black12, | ||
| blurRadius: 6, | ||
| offset: Offset(0, 3), | ||
| ), | ||
| ], | ||
| ), | ||
| child: Center( | ||
| child: | ||
| Text('Care Tokens : ${_userProfileData!.careTokens}'))), | ||
| ), | ||
| Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: SizedBox( | ||
| height: 40, | ||
| width: 150, | ||
| child: Container( | ||
| decoration: BoxDecoration( | ||
| color: const Color.fromARGB(255, 28, 211, 129), | ||
| border: Border.all( | ||
| color: Colors.black, | ||
| width: 2, | ||
| ), | ||
| Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: SizedBox( | ||
| height: 40, | ||
| width: 150, | ||
| child: Container( | ||
| decoration: BoxDecoration( | ||
| color: const Color.fromARGB(255, 251, 251, 99), | ||
| border: Border.all( | ||
| color: Colors.black, | ||
| width: 2, | ||
| ), | ||
| borderRadius: BorderRadius.circular(12), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black12, | ||
| blurRadius: 6, | ||
| offset: Offset(0, 3), | ||
| ), | ||
| borderRadius: BorderRadius.circular(12), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black12, | ||
| blurRadius: 6, | ||
| offset: Offset(0, 3), | ||
| ), | ||
| ], | ||
| ], | ||
| ), | ||
| child: Center( | ||
| child: Text( | ||
| 'Verifier Tokens : ${_userProfileData!.verifierTokens}'))), | ||
| ), | ||
| ), | ||
| Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: SizedBox( | ||
| height: 40, | ||
| width: 150, | ||
| child: Container( | ||
| decoration: BoxDecoration( | ||
| color: const Color.fromARGB(255, 28, 211, 129), | ||
| border: Border.all( | ||
| color: Colors.black, | ||
| width: 2, | ||
| ), | ||
| child: Center( | ||
| child: Text( | ||
| 'Legacy Tokens : ${_userProfileData!.legacyTokens}'))), | ||
| ), | ||
| borderRadius: BorderRadius.circular(12), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black12, | ||
| blurRadius: 6, | ||
| offset: Offset(0, 3), | ||
| ), | ||
| ], | ||
| ), | ||
| child: Center( | ||
| child: Text( | ||
| 'Legacy Tokens : ${_userProfileData!.legacyTokens}'))), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| ], | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Guard against null profile and de-duplicate token tiles
This widget dereferences _userProfileData! four times. If build() accidentally renders this on an error path, it will throw. Also, the four nearly-identical blocks can be collapsed into a single helper for maintainability.
- Widget _tokenWidget() {
- return Column(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.all(4.0),
- child: SizedBox(
- height: 40,
- width: 150,
- child: Container(
- decoration: BoxDecoration(
- color: const Color.fromARGB(255, 251, 251, 99),
- border: Border.all(
- color: Colors.black,
- width: 2,
- ),
- borderRadius: BorderRadius.circular(12),
- boxShadow: [
- BoxShadow(
- color: Colors.black12, // shadow color
- blurRadius: 6, // shadow softness
- offset: Offset(0, 3), // shadow position
- ),
- ],
- ),
- child: Center(
- child: Text(
- 'Planter Tokens : ${_userProfileData!.planterTokens}'))),
- ),
- ),
- Padding(
- padding: const EdgeInsets.all(4.0),
- child: SizedBox(
- height: 40,
- width: 150,
- child: Container(
- decoration: BoxDecoration(
- color: const Color.fromARGB(255, 28, 211, 129),
- border: Border.all(
- color: Colors.black,
- width: 2,
- ),
- borderRadius: BorderRadius.circular(12),
- boxShadow: [
- BoxShadow(
- color: Colors.black12,
- blurRadius: 6,
- offset: Offset(0, 3),
- ),
- ],
- ),
- child: Center(
- child:
- Text('Care Tokens : ${_userProfileData!.careTokens}'))),
- ),
- ),
- Padding(
- padding: const EdgeInsets.all(4.0),
- child: SizedBox(
- height: 40,
- width: 150,
- child: Container(
- decoration: BoxDecoration(
- color: const Color.fromARGB(255, 251, 251, 99),
- border: Border.all(
- color: Colors.black,
- width: 2,
- ),
- borderRadius: BorderRadius.circular(12),
- boxShadow: [
- BoxShadow(
- color: Colors.black12,
- blurRadius: 6,
- offset: Offset(0, 3),
- ),
- ],
- ),
- child: Center(
- child: Text(
- 'Verifier Tokens : ${_userProfileData!.verifierTokens}'))),
- ),
- ),
- Padding(
- padding: const EdgeInsets.all(4.0),
- child: SizedBox(
- height: 40,
- width: 150,
- child: Container(
- decoration: BoxDecoration(
- color: const Color.fromARGB(255, 28, 211, 129),
- border: Border.all(
- color: Colors.black,
- width: 2,
- ),
- borderRadius: BorderRadius.circular(12),
- boxShadow: [
- BoxShadow(
- color: Colors.black12,
- blurRadius: 6,
- offset: Offset(0, 3),
- ),
- ],
- ),
- child: Center(
- child: Text(
- 'Legacy Tokens : ${_userProfileData!.legacyTokens}'))),
- ),
- ),
- ],
- );
- }
+ Widget _tokenWidget() {
+ final profile = _userProfileData;
+ if (profile == null) return const SizedBox.shrink();
+
+ Widget tile(String label, int value, Color color) {
+ return SizedBox(
+ height: 40,
+ width: 150,
+ child: Container(
+ decoration: BoxDecoration(
+ color: color,
+ border: Border.all(color: Colors.black, width: 2),
+ borderRadius: BorderRadius.circular(12),
+ boxShadow: const [
+ BoxShadow(
+ color: Colors.black12,
+ blurRadius: 6,
+ offset: Offset(0, 3),
+ ),
+ ],
+ ),
+ child: Center(child: Text('$label : $value')),
+ ),
+ );
+ }
+
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: tile('Planter Tokens', profile.planterTokens,
+ const Color.fromARGB(255, 251, 251, 99)),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: tile('Care Tokens', profile.careTokens,
+ const Color.fromARGB(255, 28, 211, 129)),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: tile('Verifier Tokens', profile.verifierTokens,
+ const Color.fromARGB(255, 251, 251, 99)),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: tile('Legacy Tokens', profile.legacyTokens,
+ const Color.fromARGB(255, 28, 211, 129)),
+ ),
+ ],
+ );
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Widget _tokenWidget() { | |
| return Container( | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.start, | |
| children: [ | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(255, 251, 251, 99), | |
| border: Border.all( | |
| color: Colors.black, | |
| width: 2, | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black12, // shadow color | |
| blurRadius: 6, // shadow softness | |
| offset: Offset(0, 3), // shadow position | |
| ), | |
| ], | |
| return Column( | |
| mainAxisAlignment: MainAxisAlignment.start, | |
| children: [ | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(255, 251, 251, 99), | |
| border: Border.all( | |
| color: Colors.black, | |
| width: 2, | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Planter Tokens : ${_userProfileData!.planterTokens}'))), | |
| ), | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(255, 28, 211, 129), | |
| border: Border.all( | |
| color: Colors.black, | |
| width: 2, | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black12, // shadow color | |
| blurRadius: 6, // shadow softness | |
| offset: Offset(0, 3), // shadow position | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black12, | |
| blurRadius: 6, | |
| offset: Offset(0, 3), | |
| ), | |
| ], | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Care Tokens : ${_userProfileData!.careTokens}'))), | |
| ), | |
| ], | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Planter Tokens : ${_userProfileData!.planterTokens}'))), | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(255, 251, 251, 99), | |
| border: Border.all( | |
| color: Colors.black, | |
| width: 2, | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black12, | |
| blurRadius: 6, | |
| offset: Offset(0, 3), | |
| ), | |
| ], | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(255, 28, 211, 129), | |
| border: Border.all( | |
| color: Colors.black, | |
| width: 2, | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Verifier Tokens : ${_userProfileData!.verifierTokens}'))), | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black12, | |
| blurRadius: 6, | |
| offset: Offset(0, 3), | |
| ), | |
| ], | |
| ), | |
| child: Center( | |
| child: | |
| Text('Care Tokens : ${_userProfileData!.careTokens}'))), | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(255, 28, 211, 129), | |
| border: Border.all( | |
| color: Colors.black, | |
| width: 2, | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(255, 251, 251, 99), | |
| border: Border.all( | |
| color: Colors.black, | |
| width: 2, | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black12, | |
| blurRadius: 6, | |
| offset: Offset(0, 3), | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black12, | |
| blurRadius: 6, | |
| offset: Offset(0, 3), | |
| ), | |
| ], | |
| ], | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Verifier Tokens : ${_userProfileData!.verifierTokens}'))), | |
| ), | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(255, 28, 211, 129), | |
| border: Border.all( | |
| color: Colors.black, | |
| width: 2, | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Legacy Tokens : ${_userProfileData!.legacyTokens}'))), | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black12, | |
| blurRadius: 6, | |
| offset: Offset(0, 3), | |
| ), | |
| ], | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Legacy Tokens : ${_userProfileData!.legacyTokens}'))), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ); | |
| Widget _tokenWidget() { | |
| final profile = _userProfileData; | |
| if (profile == null) return const SizedBox.shrink(); | |
| Widget tile(String label, int value, Color color) { | |
| return SizedBox( | |
| height: 40, | |
| width: 150, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: color, | |
| border: Border.all(color: Colors.black, width: 2), | |
| borderRadius: BorderRadius.circular(12), | |
| boxShadow: const [ | |
| BoxShadow( | |
| color: Colors.black12, | |
| blurRadius: 6, | |
| offset: Offset(0, 3), | |
| ), | |
| ], | |
| ), | |
| child: Center(child: Text('$label : $value')), | |
| ), | |
| ); | |
| } | |
| return Column( | |
| mainAxisAlignment: MainAxisAlignment.start, | |
| children: [ | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: tile( | |
| 'Planter Tokens', | |
| profile.planterTokens, | |
| const Color.fromARGB(255, 251, 251, 99), | |
| ), | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: tile( | |
| 'Care Tokens', | |
| profile.careTokens, | |
| const Color.fromARGB(255, 28, 211, 129), | |
| ), | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: tile( | |
| 'Verifier Tokens', | |
| profile.verifierTokens, | |
| const Color.fromARGB(255, 251, 251, 99), | |
| ), | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(4.0), | |
| child: tile( | |
| 'Legacy Tokens', | |
| profile.legacyTokens, | |
| const Color.fromARGB(255, 28, 211, 129), | |
| ), | |
| ), | |
| ], | |
| ); | |
| } |
🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/profile_section_widget.dart around lines 246 to
355, the _tokenWidget dereferences _userProfileData! multiple times (risking a
crash if null) and repeats near-identical tile code; change it to first guard
against a null profile (return a placeholder/empty SizedBox or a loading/error
tile when _userProfileData is null) and refactor the repeated
Container+Padding+SizedBox pattern into a small private helper method that
accepts label, tokenValue (use null-aware access like
_userProfileData?.planterTokens), and color to build each tile, then call that
helper for the four token types to eliminate duplication and avoid
force-unwrapping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
.github/workflows/flutter.yaml (4)
20-24: Enable Flutter action caching; keep channel stable or pin a version.You already use channel: stable (good). Add cache: true for faster runs; optionally pin a 3.24.x version for reproducibility.
- name: Setup Flutter uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: 'stable' + cache: true
25-33: Run Flutter tests in CI.Tests exist in the repo; surface them to catch regressions.
- name: Dart analyze run: flutter analyze + - name: Run Flutter tests + run: flutter test -r expanded + - name: Check formatting run: dart format --output=none --set-exit-if-changed .
15-16: Upgrade checkout to v4 to avoid runner deprecation and actionlint failure.actions/checkout@v3 is flagged and will eventually break on newer runners. Switch to v4 and fetch full history when needed.
- - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0
34-35: Don’t pipe build output to grep; it can mask failures and break exit codes.Make the step fail on Flutter/Gradle errors and keep logs.
- - name: Flutter build (apk) - run: flutter build apk --release --no-tree-shake-icons | grep -v "deprecated" + - name: Flutter build (apk) + run: | + set -o pipefail + flutter build apk --release --no-tree-shake-icons 2>&1 | tee build-apk.log
🧹 Nitpick comments (6)
.github/workflows/flutter.yaml (6)
17-18: Fix YAML lint issues: trailing spaces and extra indent after colon.These are failing yamllint: remove trailing spaces and the extra space after “run:”.
- - name: Setup environment file from template - run: cp .env.stencil .env + - name: Setup environment file from template + run: cp .env.stencil .env @@ - channel: 'stable' + channel: 'stable' @@ - - name: Flutter build (apk) - run: flutter build apk --release --no-tree-shake-icons | grep -v "deprecated" + - name: Flutter build (apk) + run: flutter build apk --release --no-tree-shake-iconsIf you keep the multi-line step with tee from the prior comment, still ensure “run:” has a single space and no trailing spaces.
Also applies to: 23-23, 35-35
9-12: Harden the job: add explicit permissions and a timeout.Least-privilege and timeboxing help security and CI stability.
jobs: build: runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 30
13-13: Speed up Android builds with Java/Gradle cache.Set up Temurin JDK and enable Gradle caching to cut APK build times.
steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle'Note: Java 17 is widely compatible with recent Android Gradle Plugin; adjust if your Gradle/AGP requires a different version.
3-8: Optional: add concurrency to cancel superseded runs on the same branch/PR.Prevents wasting runners when pushing multiple commits rapidly.
on: push: branches: [ main ] pull_request: branches: [ main ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true
9-36: Optional: add a separate web build job.Your app targets web; building it in CI catches platform-specific breakages early.
jobs: build: @@ - name: Flutter build (apk) run: flutter build apk --release --no-tree-shake-icons + + build-web: + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 20 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + - name: Install dependencies + run: flutter pub get + - name: Analyze + run: flutter analyze + - name: Build web + run: flutter build web --releaseIf your web import graph includes dart:io, this will fail; consider conditional exports to separate mobile/web implementations before enabling this job.
17-19: Confirm env template sufficiency for CI builds.Copying .env.stencil implies non-secret defaults. Verify required keys (e.g., APPLICATION_ID, contract addresses) have safe placeholders that allow analyze/test/build to pass without secrets.
If needed, I can propose a minimal .env.stencil that satisfies compile-time config but gates runtime features behind feature flags.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
.github/workflows/flutter.yaml(1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/flutter.yaml
15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 YAMLlint (1.37.1)
.github/workflows/flutter.yaml
[error] 17-17: trailing spaces
(trailing-spaces)
[error] 18-18: trailing spaces
(trailing-spaces)
[error] 23-23: trailing spaces
(trailing-spaces)
[warning] 35-35: too many spaces after colon
(colons)
|
@ceilican Bhavik asked for your permission to merge it, he has reviewd it from his side. |
Solves #10
Summary by CodeRabbit
New Features
Bug Fixes
Style
Chores